Beispiel #1
0
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
}
Beispiel #2
0
// 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
}
Beispiel #3
0
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
}
Beispiel #4
0
// 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)
}
Beispiel #5
0
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
}
Beispiel #6
0
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
}
Beispiel #7
0
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
}
Beispiel #8
0
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
}
Beispiel #9
0
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
}
Beispiel #10
0
// 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
}
Beispiel #11
0
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
}
Beispiel #12
0
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
}