Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
liamg committed Oct 26, 2022
0 parents commit 1af1dd8
Show file tree
Hide file tree
Showing 17 changed files with 434 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: release

on:
push:
tags:
- v*

jobs:
build:
name: releasing
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: actions/setup-go@v3
with:
go-version: "1.19"
- uses: goreleaser/goreleaser-action@v3
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 changes: 21 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: tests

on:
pull_request:

jobs:
test:
name: tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest ]

steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '1.19'
cache: true
- name: Run tests
run: sudo -E $(which go) test ./...
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
31 changes: 31 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
builds:
- id: siphon
main: .
binary: siphon
ldflags:
- "-s -w -extldflags '-fno-PIC -static'"
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- "amd64"
- "arm64"
- "386"
- "arm"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"

archives:
- format: binary
name_template: "{{ .Binary}}-{{ .Os }}-{{ .Arch }}"

release:
prerelease: auto
github:
owner: liamg
name: siphon
24 changes: 24 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Siphon

Intercept input/output (_stdin/stdout/stderr_) for any process, even where said output is sent to _/dev/null_ or elsewhere.

![demo gif](demo.gif)

It can also be used to spy on another users shell:

![demo gif 2](demo2.gif)

Currently Siphon works on Linux, with `amd64`, `arm64`, `arm`, and `386`. Adding support for more architectures is pretty simple, feel free to raise an issue.

It uses `ptrace` which means you'll likely need to run it as `root` for the ptrace privilege.
Binary file added demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/liamg/siphon

go 1.19

require (
github.com/spf13/cobra v1.6.0
github.com/stretchr/testify v1.7.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
23 changes: 23 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
141 changes: 141 additions & 0 deletions intercept.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package main

import (
"fmt"
"os"
"runtime"
"syscall"
)

func watchProcess(pid int, stdout, stderr, stdin bool) error {
// ensure tracing all comes from same thread
runtime.LockOSThread()

if _, err := os.FindProcess(pid); err != nil {
return fmt.Errorf("could not find process with pid %d: %w", pid, err)
}

if err := syscall.PtraceAttach(pid); err == syscall.EPERM {
return fmt.Errorf("could not attach to process with pid %d: %w - check your permissions", pid, err)
} else if err != nil {
return err
}

status := syscall.WaitStatus(0)
if _, err := syscall.Wait4(pid, &status, 0, nil); err != nil {
return err
}

defer func() {
_ = syscall.PtraceDetach(pid)
_, _ = syscall.Wait4(pid, &status, 0, nil)
}()

// deliver SIGTRAP|0x80
if err := syscall.PtraceSetOptions(pid, syscall.PTRACE_O_TRACESYSGOOD); err != nil {
return err
}

for {
fd, data, err := interceptReadsAndWrites(pid)
if err != nil {
return err
}

if stdout && fd == uint64(syscall.Stdout) || stderr && fd == uint64(syscall.Stderr) || stdin && fd == uint64(syscall.Stdin) {
if fd == uint64(syscall.Stdin) {
fd = uint64(os.Stdin.Fd())
}
_, _ = fmt.Fprintf(os.NewFile(uintptr(fd), "pipe"), "%s", string(data))
}
}
}

func interceptReadsAndWrites(pid int) (fd uint64, data []byte, err error) {

// intercept syscall
err = syscall.PtraceSyscall(pid, 0)
if err != nil {
return 0, nil, fmt.Errorf("could not intercept syscall: %w", err)
}

// wait for a syscall
status := syscall.WaitStatus(0)
_, err = syscall.Wait4(pid, &status, 0, nil)
if err != nil {
return 0, nil, err
}

// if interrupted, stop tracing
if status.StopSignal().String() == "interrupt" {
_ = syscall.PtraceSyscall(pid, int(status.StopSignal()))
return 0, nil, fmt.Errorf("process interrupted")
}

defer func() {

// continue the syscall we intercepted
err = syscall.PtraceSyscall(pid, 0)
if err != nil {
err = fmt.Errorf("could not continue process: %w", err)
return
}

// and wait for it to finish
status := syscall.WaitStatus(0)
_, err = syscall.Wait4(pid, &status, 0, nil)
if err != nil {
err = fmt.Errorf("could not wait for process: %w", err)
return
}

// process exited
if status.Exited() {
err = fmt.Errorf("process exited")
return
}

// if interrupted, stop tracing
if status.StopSignal().String() == "interrupt" {
_ = syscall.PtraceSyscall(pid, int(status.StopSignal()))
err = fmt.Errorf("process interrupted")
return
}
}()

// if we have a syscall, examine it...
if status.TrapCause()&int(syscall.SIGTRAP|0x80) > 0 {

// read registers
regs := &syscall.PtraceRegs{}
if err := syscall.PtraceGetRegs(pid, regs); err != nil {
return 0, nil, err
}

// find the syscall number for the host architecture
syscallNo := grabSyscallNo(regs)

// if it's a read/write syscall, grab the args
switch syscallNo {
case syscall.SYS_READ, syscall.SYS_WRITE:

// grab the args to WRITE for the host architecture
// fd == file descriptor (generally 1 for stdout, 2 for stderr)
// ptr == pointer to the buffer
// lng == length of the buffer
fd, ptr, lng := grabArgsFromRegs(regs)

// if we want to see this output, read it from memory
if lng > 0 {
data := make([]byte, lng)
if _, err := syscall.PtracePeekData(pid, uintptr(ptr), data); err != nil {
return 0, nil, err
}
return fd, data, nil
}
}

}

return 0, nil, err
}
39 changes: 39 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"fmt"
"strconv"

"github.com/spf13/cobra"
)

var cmd = &cobra.Command{
Use: "siphon [pid]",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
cmd.SilenceErrors = true
pid, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid pid: %w", err)
}
return watchProcess(int(pid), flagStdOut, flagStdErr, flagStdIn)
},
}

var (
flagStdOut = true
flagStdErr = true
flagStdIn = false
)

func main() {

cmd.Flags().BoolVarP(&flagStdOut, "stdout", "o", flagStdOut, "Show stdout")
cmd.Flags().BoolVarP(&flagStdErr, "stderr", "e", flagStdErr, "Show stderr")
cmd.Flags().BoolVarP(&flagStdIn, "stdin", "i", flagStdIn, "Show stdin")

if err := cmd.Execute(); err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Error: %v", err)
}
}
15 changes: 15 additions & 0 deletions registers_386.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build 386

package main

import (
"syscall"
)

func grabSyscallNo(regs *syscall.PtraceRegs) uint64 {
return uint64(regs.Orig_eax)
}

func grabArgsFromRegs(regs *syscall.PtraceRegs) (fd, ptr, lng uint64) {
return uint64(regs.Ebx), uint64(regs.Ecx), uint64(regs.Edx)
}
15 changes: 15 additions & 0 deletions registers_amd64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build amd64

package main

import (
"syscall"
)

func grabSyscallNo(regs *syscall.PtraceRegs) uint64 {
return regs.Orig_rax
}

func grabArgsFromRegs(regs *syscall.PtraceRegs) (fd, ptr, lng uint64) {
return regs.Rdi, regs.Rsi, regs.Rdx
}
15 changes: 15 additions & 0 deletions registers_arm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build arm

package main

import (
"syscall"
)

func grabSyscallNo(regs *syscall.PtraceRegs) uint64 {
return uint64(regs.Uregs[7])
}

func grabArgsFromRegs(regs *syscall.PtraceRegs) (fd, ptr, lng uint64) {
return uint64(regs.Uregs[0]), uint64(regs.Uregs[1]), uint64(regs.Uregs[2])
}
Loading

0 comments on commit 1af1dd8

Please sign in to comment.