christofsteel / pyautosplit

Autosplitter for Linux (for the LiveSplit Server)
Apache License 2.0
45 stars 5 forks source link

Allow tracking wine executables #12

Closed Lucki closed 3 years ago

Lucki commented 3 years ago

Tracking wine processes seems not possible in the current state because of the way wine spawns the executable.

This PR allows to track wine (or any other executable) by providing an optional wrapper binary which then gets the capability cap_sys_ptrace=ep and allows execution without root privileges. While this would be possible without the wrapper it would be necessary to give python this capability - making essentially all memory accessible to all python scripts. While this is probably a security issue for whole python I hope to limit the security issue with this wrapper and by making it very clear in the readme that this wrapper and dependent scripts (pyautosplit, python-ptrace, …) should be secured against malicious activity.

To know wether PyAutoSplit is called by the wrapper I've added a silent argument --from-wrapper. Together with an exe in the game json the script goes into the "wrapper" mode and attaches to the pid of the given executable instead of the direct child process.

While at it I also added the possibility to add environment variables in the game json (like WINEPREFIX=/path/asd):

"env": {
    "WINEPREFIX": "/path/asd"
}
christofsteel commented 3 years ago

Hm. I don't like this solution at all. I think you can "smuggle in" malicious python modules, that replace some of the system wide modules. And with that you can easily build a complete memory watcher. (Even without touching pyautosplit and the wrapper...)

Have you tried using PTRACE_O_TRACEFORK? I think you can enable it in python-ptrace with PtraceDebugger.traceFork() Unfortunately the documentation for python-ptrace is less than stellar, but according to the comments in the code, this should be exactly, what is needed.

Lucki commented 3 years ago

Have you tried using PTRACE_O_TRACEFORK? I think you can enable it in python-ptrace with PtraceDebugger.traceFork() Unfortunately the documentation for python-ptrace is less than stellar, but according to the comments in the code, this should be exactly, what is needed.

Yes, I've tried that and unfortunately it's not working that way. Most of the time there's the following error despite I'm not using breakpoints:

line 93, in handle_breakpoints
    varname = self.breakpoints[rip]
KeyError: 140711710601359

But I also got a few clean startups but the values which it has to read are all None:

readBytes(0x0000000000b7cb84, 4) error: [Errno 5] Input/output error

Hm. I don't like this solution at all. I think you can "smuggle in" malicious python modules, that replace some of the system wide modules. And with that you can easily build a complete memory watcher. (Even without touching pyautosplit and the wrapper...)

You have much bigger problems if a file by root is changed maliciously than this script reading memory. But you're right in user space, according to this comment I have to clean up PYTHONPATH (maybe more) or somehow start cpython with -E.

christofsteel commented 3 years ago

You have much bigger problems if a file by root is changed maliciously than this script reading memory

The problem is, that you do not need to overwrite any file owned by root and do you not even have to be root. PYTHONPATH is only one way to inject malicious modules. If this binary is linked dynamically to libpython, one could override the librarypath, to use ones own malicious libpython. And if we link it statically, I fear this will be a deployment nightmare. One way to avoid all of this, would be to run pyautosplit via sudo, like GameConqueror does, but I want to avoid that. An autosplitter does not need to have be superuser, or read all of memory, but only the memory of some dedicated processes. And this should be possible with user rights.

Concerning the error with the breakpoint, I think this comes from the fact, that the architecture of pyautosplit just cannot deal with multiple/switching processes. But I think that can be added.

Maybe we should start with building a "proof of concept" memory reader for wine processes, and then add it to the project.

I also tried to read the memory of a wine process with GameConqueror, and also just got zeroes, but maybe I was doing something wrong.

Lucki commented 3 years ago

PYTHONPATH is only one way to inject malicious modules. If this binary is linked dynamically to libpython, one could override the librarypath, to use ones own malicious libpython. And if we link it statically, I fear this will be a deployment nightmare.

Unfortunately, I don't know enough about these things to be of any further help here.

Edit: It seems user namespaces would limit cap_sys_ptrace to processes in the same namespace. But it looks really complicated.

Having a capability inside a user namespace permits a process to perform operations (that require privilege) only on resources governed by that namespace. In other words, having a capability in a user namespace permits a process to perform privileged operations on resources that are governed by (nonuser) namespaces owned by (associated with) the user namespace (see the next subsection).

One way to avoid all of this, would be to run pyautosplit via sudo, like GameConqueror does, but I want to avoid that. An autosplitter does not need to have be superuser, or read all of memory, but only the memory of some dedicated processes.

AFAIK that's what these capabilities are trying to solve. These allow ping to run without root permissions for example.

I also tried to read the memory of a wine process with GameConqueror, and also just got zeroes, but maybe I was doing something wrong.

Only zeroes does indeed sounds strange. At my testing I've seen the address is a bit shifted compared to the address they're using in the LiveSplit component (0x64A0600xA4A060). Might be the same for your test.

Generally the way to view a known values is: 1. Attach to the process 1. Add the address to the list below (If the address isn't known yet the search process has to take place beforehand) ![gameconqueror](https://user-images.githubusercontent.com/1408843/133429501-12cefbdd-87fb-4bbd-89be-0f3b4ac02913.png)
Lucki commented 3 years ago

It seems ptrace can't follow the wine process because the way it get set up. The preloader doesn't use fork() but prepares and allocates all necessary memory beforehand and somehow starts the process from this. But I also can be completely wrong on this.

I guess it would be possible to hook into this by

Some links I stumbled on: