func copyMemory(p process.Process, address uintptr, buffer []byte) (harderror error, softerrors []error) { buf := unsafe.Pointer(&buffer[0]) n := len(buffer) var bytesRead C.size_t resp := C.copy_process_memory( (C.process_handle_t)(p.Handle()), C.memory_address_t(address), C.size_t(n), buf, &bytesRead, ) harderror, softerrors = cresponse.GetResponsesErrors(unsafe.Pointer(resp)) C.response_free(resp) if harderror != nil { harderror = fmt.Errorf("Error while copying %d bytes starting at %x: %s", n, address, harderror.Error()) return } if len(buffer) != int(bytesRead) { harderror = fmt.Errorf("Could not copy %d bytes starting at %x, copyed %d", len(buffer), address, bytesRead) } return }
// checkLibraries retrieves the linked libraries of a process and compares them with the // regexes of library checks func (s search) checkLibraries(proc process.Process, procname string) (matchedall bool) { matchedall = true if s.checkmask&checkLib == 0 { // this search has no library check return } for i, c := range s.checks { if c.code&checkLib == 0 { continue } libs, err, serr := listlibs.GetMatchingLoadedLibraries(proc, c.regex) if err != nil { stats.Failures = append(stats.Failures, err.Error()) } if len(serr) > 0 && s.Options.LogFailures { stats.Failures = append(stats.Failures, err.Error()) if debug { for _, err := range serr { fmt.Printf("checkLibraries: soft error -> %v\n", err) } } } if len(libs) > 0 { if debug { fmt.Printf("checkLibraries: proc name '%s' pid %d has libraries matching regex '%s'\n", procname, proc.Pid(), c.value) } c.storeMatch(proc) } else { matchedall = false } s.checks[i] = c } return }
func listLoadedLibraries(p process.Process) (libraries []string, harderror error, softerrors []error) { mapsFile, harderror := os.Open(common.MapsFilePathFromPid(p.Pid())) if harderror != nil { return } defer mapsFile.Close() scanner := bufio.NewScanner(mapsFile) processName, harderror, softerrors := p.Name() if harderror != nil { return } libs := make([]string, 0, 10) for scanner.Scan() { line := scanner.Text() items := common.SplitMapsFileEntry(line) if len(items) != 6 { return libs, fmt.Errorf("Unrecognised maps line: %s", line), softerrors } path := items[5] if path == processName { continue } if path == "/dev/zero" || path == "/dev/zero (deleted)" { continue } if path == "" { continue } if path[0] == '[' { continue } if inSlice(path, libs) { continue } libs = append(libs, path) } return libs, nil, nil }
// evaluateProcess takes a single process and applies searches to it. All searches are evaluated. The `name` and `library` // checks are run first, and if needed, the memory of the process is read to run the checks on `contents` and `bytes`. // The logic is optimized to only read the process memory once and apply all the checks to it. func (r Runner) evaluateProcess(proc process.Process) (err error) { if !debug { defer func() { if e := recover(); e != nil { err = fmt.Errorf("evaluateProcess() -> %v", e) } }() } procname, err, serr := proc.Name() if err != nil { return } for _, err = range serr { stats.Failures = append(stats.Failures, err.Error()) if debug { fmt.Printf("evaluateProcess: soft error -> %v\n", err) } } if debug { fmt.Printf("evaluateProcess: evaluating proc %s\n", procname) } // first pass: apply all name & library checks against the current process for label, search := range r.Parameters.Searches { if !search.isactive { goto skip } if !search.checkName(proc, procname) && search.Options.MatchAll { if debug { fmt.Printf("evaluateProcess: proc %s does not match the names of search %s and matchall is set\n", procname, label) } search.deactivate() goto skip } if !search.checkLibraries(proc, procname) && search.Options.MatchAll { if debug { fmt.Printf("evaluateProcess: proc %s does not match the libraries of search %s and matchall is set\n", procname, label) } search.deactivate() goto skip } skip: r.Parameters.Searches[label] = search } // second pass: walk the memory of the process and apply contents regexes and bytes searches return r.walkProcMemory(proc, procname) }
func (c *check) storeMatch(proc process.Process) { if debug { fmt.Printf("storing process id %d that matched check %d\n", proc.Pid(), c.code) } store := true for _, storedPs := range c.matchedPs { // only store files once per check if proc.Pid() == storedPs.Pid() { store = false } } if store { c.matched++ c.matchedPs = append(c.matchedPs, proc) } return }
func listLoadedLibraries(p process.Process) (libraries []string, harderror error, softerrors []error) { r := C.getModules(C.process_handle_t(p.Handle())) defer C.EnumProcessModulesResponse_Free(r) if r.error != 0 { return nil, fmt.Errorf("getModules failed with error: %d", r.error), nil } mods := make([]string, r.length) // We use this to access C arrays without doing manual pointer arithmetic. cmods := *(*[]C.ModuleInfo)(unsafe.Pointer( &reflect.SliceHeader{ Data: uintptr(unsafe.Pointer(r.modules)), Len: int(r.length), Cap: int(r.length)})) for i, _ := range mods { mods[i] = C.GoString(cmods[i].filename) } return mods, nil, nil }
func listLoadedLibraries(p process.Process) (libraries []string, harderror error, softerrors []error) { var ptr uintptr var sizeT C.size_t clibs := (***C.char)(C.malloc(C.size_t(unsafe.Sizeof(ptr)))) count := (*C.size_t)(C.malloc(C.size_t(unsafe.Sizeof(sizeT)))) defer C.free_loaded_libraries_list(*clibs, *count) defer C.free(unsafe.Pointer(clibs)) defer C.free(unsafe.Pointer(count)) response := C.list_loaded_libraries((C.process_handle_t)(p.Handle()), clibs, count) harderror, softerrors = cresponse.GetResponsesErrors(unsafe.Pointer(response)) C.response_free(response) if harderror != nil { return } libraries = make([]string, 0, *count) clibsSlice := *(*[]*C.char)(unsafe.Pointer( &reflect.SliceHeader{ Data: uintptr(unsafe.Pointer(*clibs)), Len: int(*count), Cap: int(*count)})) processName, harderror, softs := p.Name() if harderror != nil { return } softerrors = append(softerrors, softs...) for i, _ := range clibsSlice { if clibsSlice[i] == nil { continue } str := C.GoString(clibsSlice[i]) if str == processName { continue } libraries = append(libraries, str) } return }
func nextReadableMemoryRegion(p process.Process, address uintptr) (region MemoryRegion, harderror error, softerrors []error) { var isAvailable C.bool var cRegion C.memory_region_t response := C.get_next_readable_memory_region( (C.process_handle_t)(p.Handle()), C.memory_address_t(address), &isAvailable, &cRegion) harderror, softerrors = cresponse.GetResponsesErrors(unsafe.Pointer(response)) C.response_free(response) if harderror != nil || isAvailable == false { return NoRegionAvailable, harderror, softerrors } return MemoryRegion{uintptr(cRegion.start_address), uint(cRegion.length)}, harderror, softerrors }
func copyMemory(p process.Process, address uintptr, buffer []byte) (harderror error, softerrors []error) { mem, harderror := os.Open(common.MemFilePathFromPid(p.Pid())) if harderror != nil { harderror := fmt.Errorf("Error while reading %d bytes starting at %x: %s", len(buffer), address, harderror) return harderror, softerrors } defer mem.Close() bytes_read, harderror := mem.ReadAt(buffer, int64(address)) if harderror != nil { harderror := fmt.Errorf("Error while reading %d bytes starting at %x: %s", len(buffer), address, harderror) return harderror, softerrors } if bytes_read != len(buffer) { return fmt.Errorf("Could not read the entire buffer"), softerrors } return nil, softerrors }
// checkName compares the "name" (binary full path) of a process against name checks func (s search) checkName(proc process.Process, procname string) (matchedall bool) { matchedall = true if s.checkmask&checkName == 0 { // this search has no name check return } for i, c := range s.checks { if c.code&checkName == 0 { continue } if debug { fmt.Println("checkName: evaluating", procname, proc.Pid(), "against check", c.value) } if c.regex.MatchString(procname) { if debug { fmt.Printf("checkName: proc name '%s' pid %d matches regex '%s'\n", procname, proc.Pid(), c.value) } c.storeMatch(proc) } else { if debug { fmt.Printf("checkName: proc name '%s' pid %d does not match regex '%s'\n", procname, proc.Pid(), c.value) } matchedall = false } s.checks[i] = c } return }
func nextReadableMemoryRegion(p process.Process, address uintptr) (region MemoryRegion, harderror error, softerrors []error) { mapsFile, harderror := os.Open(common.MapsFilePathFromPid(p.Pid())) if harderror != nil { return } defer mapsFile.Close() region = MemoryRegion{} scanner := bufio.NewScanner(mapsFile) for scanner.Scan() { line := scanner.Text() items := common.SplitMapsFileEntry(line) if len(items) != 6 { return region, fmt.Errorf("Unrecognised maps line: %s", line), softerrors } start, end, err := common.ParseMapsFileMemoryLimits(items[0]) if err != nil { return region, err, softerrors } if end <= address { continue } // Skip vsyscall as it can't be read. It's a special page mapped by the kernel to accelerate some syscalls. if items[5] == "[vsyscall]" { continue } // Check if memory is unreadable if items[1][0] == '-' { // If we were already reading a region this will just finish it. We only report the softerror when we // were actually trying to read it. if region.Address != 0 { return region, nil, softerrors } softerrors = append(softerrors, fmt.Errorf("Unreadable memory %s", items[0])) continue } size := uint(end - start) // Begenning of a region if region.Address == 0 { region = MemoryRegion{Address: start, Size: size} continue } // Continuation of a region if region.Address+uintptr(region.Size) == start { region.Size += size continue } // This map is outside the current region, so we are ready return region, nil, softerrors } // No region left if err := scanner.Err(); err != nil { return NoRegionAvailable, err, softerrors } // The last map was a valid region, so it was not closed by an invalid/non-contiguous one and we have to return it if region.Address > 0 { return region, harderror, softerrors } return NoRegionAvailable, nil, softerrors }
func (r Runner) walkProcMemory(proc process.Process, procname string) (err error) { // find longest byte string to search for, which determines the buffer size bufsize := uint(4096) // find lowest offset, which determines start address offset := ^uintptr(0) >> 1 // verify that at least one search is interested in inspecting the memory // of this process shouldWalkMemory := false // if at least one search wants to log failures, do so, otherwise omit them logFailures := false for label, search := range r.Parameters.Searches { // if the search is not active or the search as no content or by check to run, skip it if !search.isactive || (search.checkmask&checkContent == 0 && search.checkmask&checkByte == 0) { search.deactivate() r.Parameters.Searches[label] = search continue } shouldWalkMemory = true // find the largest bufsize needed for _, c := range search.checks { if c.code&checkByte != 0 { if uint(len(c.bytes)) > (bufsize / 2) { bufsize = 2 * uint(len(c.bytes)) // pad to always have an even bufsize if bufsize%2 != 0 { bufsize++ } } } } // find the smallest offset needed if uintptr(search.Options.Offset) < offset { offset = uintptr(search.Options.Offset) } if search.Options.LogFailures { logFailures = true } } if !shouldWalkMemory { if debug { fmt.Println("walkProcMemory: no check needs to read the memory of process", proc.Pid(), procname) } return } // keep track of the number of bytes read to exit when maxlength is reached var readBytes float64 walkfn := func(curStartAddr uintptr, buf []byte) (keepSearching bool) { if readBytes == 0 { readBytes += float64(len(buf)) } else { readBytes += float64(len(buf) / 2) } if debug { fmt.Println("walkProcMemory: reading", bufsize, "bytes starting at addr", curStartAddr, "; read", readBytes, "bytes so far") } for label, search := range r.Parameters.Searches { matchedall := true if !search.isactive { continue } // if the search is meant to stop at a given address, and we're passed // that point then deactivate the search now if readBytes >= search.Options.MaxLength { search.deactivate() goto skip } keepSearching = true for i, c := range search.checks { switch c.code { case checkContent: if c.regex.FindIndex(buf) == nil { // not found matchedall = false continue } c.storeMatch(proc) search.checks[i] = c case checkByte: if bytes.Index(buf, c.bytes) < 0 { // not found matchedall = false continue } c.storeMatch(proc) search.checks[i] = c } } // if all the checks have matched on this search, deactivate it if matchedall { search.deactivate() } skip: r.Parameters.Searches[label] = search } if debug && !keepSearching { fmt.Println("walkProcMemory: stopping the memory search for", proc.Pid(), procname) } return } if debug { fmt.Println("walkProcMemory: reading memory of", proc.Pid(), procname) } err, serr := memaccess.SlidingWalkMemory(proc, offset, bufsize, walkfn) if err != nil { return err } if logFailures { for _, err = range serr { stats.Failures = append(stats.Failures, err.Error()) if debug { fmt.Printf("walkProcMemory: soft error -> %v\n", err) } } } stats.MemoryRead += readBytes return }