Skip to content

Commit

Permalink
Merge branch 'release/v0.14.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
targodan committed Jan 13, 2022
2 parents 9645fd4 + 4acbde7 commit 0bbff11
Show file tree
Hide file tree
Showing 10 changed files with 413 additions and 95 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ COMMANDS:
zip-rules creates an encrypted zip containing compiled yara rules
join joins dumps with padding
crash-process, crash crash a process
as-service executes yapscan as a windows service (windows only)
help, h Shows a list of commands or help for one command
```

Expand Down Expand Up @@ -105,6 +106,32 @@ yapscan scan -r rules.zip --filter-permissions-exact rx --all-processes
yapscan --log-level debug --log-path yapscan.log scan -r rules.zip --full-report --store-dumps --all-processes
```

## Running as Service

Yapscan can be run as a windows service in order to gain SYSTEM privileges.
This allows you to crash even other windows services, using the crash command.
Running as service is currently an **experimental feature**.

For memory scanning this should not be necessary.
In my experiments it has been sufficient to run yapscan as administrator in order to read the memory of any process.
If you find a process that yapscan cannot scan with administrator privileges but that can be scanned as a service, please let me know in the [issues](https://github.com/fkie-cad/yapscan/issues/new).

In order to use yapscan as a service just prepend the `as-service` command to the command (and flags) you wish to execute.
Example:

```shell
# Normal mode
.\yapscan.exe crash 42
# Service mode
.\yapscan.exe as-service crash 42
```

The output of the windows service is transmitted to the terminal via two TCP connections.
If this breaks a warning will be emitted.
In such a case the service may still be running, you just won't see any output.
Also CTRL-C will break the proxy command, preventing you from seeing any output, but will not affect the running service.
If you want to kill the service, you'll have to use the windows service manager for now.

## Executable DLL

**The DLL built by this project is not a usual DLL, meant for importing functions from.**
Expand All @@ -126,7 +153,7 @@ extern void run(HWND hWnd, HINSTANCE hInst, LPTSTR lpCmdLine, int nCmdShow);
Some environments like VDIs (Virtual Desktop Infrastructure) may prevent the execution of arbitrary exe-files but still allows for use of arbitrary DLLs.
If you gain access to a command line terminal in such an environment you can call yapscan via the built DLL like so.
```
```shell
rundll32.exe yapscan.dll,run scan -r rules.zip --all-processes
```

Expand Down
18 changes: 9 additions & 9 deletions acceptanceTests/reports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestMatchIsFound(t *testing.T) {
"--filter-size-max", maxSizeFilter,
strconv.Itoa(pid)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand Down Expand Up @@ -90,7 +90,7 @@ func TestMatchIsFound_Fuzzy(t *testing.T) {
"--filter-size-max", maxSizeFilter,
strconv.Itoa(pid)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand Down Expand Up @@ -133,7 +133,7 @@ func TestDoesNotMatchFalsePositive_Fuzzy(t *testing.T) {
"--filter-size-max", maxSizeFilter,
strconv.Itoa(pid)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand Down Expand Up @@ -164,7 +164,7 @@ func TestFullReportIsWritten_Unencrypted(t *testing.T) {
"--full-report", "--report-dir", reportDir,
strconv.Itoa(pid)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand All @@ -190,7 +190,7 @@ func TestFullReportIsWritten_Unencrypted_WhenScanningTwoProcesses(t *testing.T)
"--full-report", "--report-dir", reportDir,
strconv.Itoa(matchingPID), strconv.Itoa(nonMatchingPID)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand All @@ -215,7 +215,7 @@ func TestFullReportIsWritten_Unencrypted_WhenScanningTwoProcesses(t *testing.T)
"--full-report", "--report-dir", reportDir,
strconv.Itoa(nonMatchingPID), strconv.Itoa(matchingPID)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand Down Expand Up @@ -244,7 +244,7 @@ func TestPasswordProtectedFullReport(t *testing.T) {
"--full-report", "--report-dir", reportDir,
strconv.Itoa(pid)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand Down Expand Up @@ -273,7 +273,7 @@ func TestPGPProtectedFullReport(t *testing.T) {
"--full-report", "--report-dir", reportDir,
strconv.Itoa(pid)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand All @@ -300,7 +300,7 @@ func TestAnonymizedFullReport(t *testing.T) {
"--full-report", "--report-dir", reportDir,
strconv.Itoa(pid)}
ctx, cancel := context.WithTimeout(context.Background(), yapscanTimeout)
err := app.MakeApp(args).RunContext(ctx, args)
err := app.MakeApp().RunContext(ctx, args)
cancel()

cleanupCapture()
Expand Down
25 changes: 15 additions & 10 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func askYesNoAlwaysNever(msg string) (yes bool, always bool, never bool) {
return
}

func MakeApp(args []string) *cli.App {
func MakeApp() *cli.App {
suspendFlags := []cli.Flag{
&cli.BoolFlag{
Name: "suspend",
Expand Down Expand Up @@ -454,18 +454,23 @@ func MakeApp(args []string) *cli.App {
if runtime.GOOS == "windows" {
app.Commands = append(app.Commands,
&cli.Command{
Name: "as-service",
Usage: "executes yapscan as a windows service",
Name: "as-service",
Usage: "executes yapscan as a windows service",
SkipFlagParsing: true,
Action: func(c *cli.Context) error {
// This is a dummy
return cli.Exit("\"as-service\" must be the first argument", 1)
if len(os.Args) < 2 || os.Args[1] != "as-service" {
return cli.Exit("\"as-service\" must be the first argument", 1)
}
if len(os.Args) == 2 {
return cli.Exit("not enough arguments for \"as-service\", "+
"please provide the command to execute as a service", 1)
}
args := make([]string, 0, len(os.Args)-1)
args = append(args, os.Args[0])
args = append(args, os.Args[2:]...) // Cut out the "as-service" argument
return asService(args)
},
})

if len(args) >= 2 && args[1] == "as-service" {
asService(args)
return nil
}
}

return app
Expand Down
113 changes: 97 additions & 16 deletions app/asService.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package app

import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"sync"
"time"

"github.com/fkie-cad/yapscan/service/output"

"github.com/urfave/cli/v2"
)

func run(cmdName string, cmdArgs ...string) error {
Expand All @@ -14,42 +23,114 @@ func run(cmdName string, cmdArgs ...string) error {
return cmd.Run()
}

func asService(args []string) {
func asService(args []string) error {
serviceName := "yapscan"

binpath, err := filepath.Abs(args[0])
if err != nil {
fmt.Printf("ERROR: Could not determine absolute path of yapscan.exe, %v\n", err)
os.Exit(1)
return cli.Exit(fmt.Sprintf("ERROR: Could not determine absolute path of yapscan.exe, %v\n", err), 1)
}

args = args[2:]
scStartArguments := []string{"start", serviceName}
scStartArguments = append(scStartArguments, args...)

fmt.Println("WARNING: This feature is experimental!")
fmt.Println("WARNING: You will not see any output of the executed service. Using --log-path is strongly advised.")

fmt.Println("Removing service in case it exists already...")
run("sc.exe", "delete", "yapscan")
fmt.Println("Done removing.")
err = run("sc.exe", "delete", "yapscan")
if err != nil {
fmt.Printf("WARNING: Could not remove service, %v\n", err)
} else {
fmt.Println("Done removing.")
}

fmt.Println("Installing service...")
err = run("sc.exe", "create", serviceName, "type=", "own", "start=", "demand", "binpath=", binpath)
if err != nil {
fmt.Println("FAILURE")
fmt.Print(err)
os.Exit(10)
return cli.Exit(fmt.Sprintf("FAILURE: %v\n", err), 10)
}
fmt.Println("Done installing service.")

fmt.Println("Starting output proxy...")
proxy := output.NewOutputProxyServer()
err = proxy.Listen()
defer proxy.Close()

outputReadyForConnection := &sync.WaitGroup{}
connectionSuccess := false
waitConnection := &sync.WaitGroup{}

if err != nil {
proxy = nil
fmt.Printf("WARNING: Could not start output proxy, the service will still be started, but no output will be visible. Reason: %v\n", err)
} else {
outputReadyForConnection.Add(1)
waitConnection.Add(1)
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

defer func() {
waitConnection.Done()
}()

outputReadyForConnection.Done()
var err error
err = proxy.WaitForConnection(ctx)

if errors.Is(ctx.Err(), context.DeadlineExceeded) {
fmt.Println("WARNING: Output proxy connection timed out. The service was likely still started, but no output will be visible.")
return
}
if err != nil {
fmt.Printf("WARNING: Service did not connect to output proxy. The service was likely still started, but no output will be visible. Reason: %v\n", err)
return
}

connectionSuccess = true
}()
fmt.Println("Done, proxy is waiting for connections.")
}

scStartArguments := []string{"start", serviceName}
if proxy != nil {
scStartArguments = append(scStartArguments, strconv.Itoa(proxy.StdoutPort()), strconv.Itoa(proxy.StderrPort()))
} else {
scStartArguments = append(scStartArguments, "0", "0")
}
scStartArguments = append(scStartArguments, args...)

outputReadyForConnection.Wait()

fmt.Println("Starting service with arguments...")
err = run("sc.exe", scStartArguments...)
if err != nil {
fmt.Println("FAILURE")
fmt.Print(err)
os.Exit(11)
return cli.Exit(fmt.Sprintf("FAILURE: %v\n", err), 11)
}
fmt.Println("Done starting service, yapscan should now be running as a service with the following arguments")
fmt.Println(args)

if proxy != nil {
waitConnection.Wait()
if !connectionSuccess {
return nil
}

fmt.Println()
fmt.Println("========== Yapscan Service Output ==========")

outputDone := &sync.WaitGroup{}
outputDone.Add(1)
go func() {
var err error
defer func() {
outputDone.Done()
}()

err = proxy.ReceiveAndOutput(context.Background(), os.Stdout, os.Stderr)
if err != nil {
fmt.Printf("WARNING: Output proxy connection broke. The service might still be running, but you will not see any further output. Reason: %v\n", err)
}
}()
outputDone.Wait()
}

return nil
}
Loading

0 comments on commit 0bbff11

Please sign in to comment.