Golang for infosec — Building an EDR #Part1 —processes memory

Jean-Pierre GARNIER
5 min readJan 2, 2021

Despite all the difficulties of 2020, I enjoy learning new things every day and that’s why I decided to learn Go this year. What could be better than a concrete project to embark on such an adventure and if you want to start, you might as well do it with a big project: what if we try to build a EDR?

EDR for Endpoint Detection and Response, also known as endpoint threat detection and response, is a cyber technology that continually monitors and responds to mitigate cyber threats. It is distinguished by its capacities of detection, investigation and finally remediation.

In order for malware to carry out its actions, it will at some point be loaded into memory. Whether it is an independent process or it does it through the memory of another one, it’s there, somewhere, and we just have to find it.

Microsoft Windows is a widely used system, both by private individuals and professionals. As such, this operating system is highly susceptible to all types of threats. I won’t get into the debate about whether it has been coded or not, but it is a fact that many malwares are developed to target Windows. It is on this system that I will focus.

So what’s the plan? First, you have to list running processes. Then, any process has a reserved space in the computer memory, so from this processes list, you will have to dump their reserved memory and then analyze it. This is exactly what I set out to do first.

To be clear from the outset, I do not claim to be an expert in anything, and the purpose of this serie of articles will be to present how I decided to carry out an analysis software for my personal needs. You may disagree with me or perhaps you will find that I approach certain points too superficially. This may be true and please do not hesitate to let me know. As I said at the beginning of the article, every day is a chance to improve and learn and I will be happy to do so thanks to you. On the other hand, if I have some knowledge of english it is not my native language, please excuse me for some sentences that would not be correct.

First of all, force yourself to love the Win32 API

Developing an EDR requires a great deal of research into how the operating system works and how to access its primary functionality. You are unlikely to have third party libraries to help you and you will need to use the operating system APIs. Go is a very powerful language but sometimes lacks a very high level API, nevertheless it is one of the best ways to understand your code from end to end.

Is it time to start reinventing the wheel? Partly yes. Go’s windows package (golang.org/x/sys/windows) already provides some useful implementations, but in our case it won’t be enough. You will also find on Github several projects that implement functions and structures of the Win32 API but in my case none of them were exhaustive enough, that’s why I preferred to rely only on the Go windows package and develop the missing parts for my needs.

My first task was to take an interest in Windows processes and understand what kind of information was available with what level of privileges. Then I had to figure out how to best request the selected information and then use it. At each step the Win32 API proved to be a real help. Justen Walker has done an incredible job to explain how to handle Windows API with the golang syscall package. I won’t explain it in this article but I refer you to his article, which is really of great quality: https://medium.com/@justen.walker/breaking-all-the-rules-using-go-to-call-windows-api-2cbfd8c79724

List processes and read their memory

No need to get hurt from the start, in the first instance we can use Go to list the processes.

// GetProcessesList return PID from running processesfunc GetProcessesList() (procsIds []uint32, bytesReturned uint32, err error) {
procsIds = make([]uint32, 2048)
err = windows.EnumProcesses(procsIds, &bytesReturned)
return procsIds, bytesReturned, err
}

Now, we have processes IDs but it’s pretty nothing. From this identifiers we will need handles. Handles are integer values that identifies something for Windows in order to interact with a component. Handles are associated to rights and can refer to windows, processes, thread, modules …

For each process identifier retrieved from the previous function, you’ll need to get a process handle. This handle will be created for your specific needs only, so that’s why you’ll have to specify what you want. In this case i choosed to get PROCESS_QUERY_INFORMATION and PROCESS_VM_READ to access processes memory.

procHandle, err := GetProcessHandle(procsIds[i], windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ)if err != nil && verbose {
log.Println("[ERROR]", "PID", procsIds[i], err)
}

If the windows package of Go was sufficient to list the processes and get proc handles, you will need to implement some functions of the Win32 API in order to access processes memory. In particular, you should document yourself on the following functions:

With Justen Walker’s article you could implement all of these function. If you want to see the final result, just jump there: https://github.com/codeyourweb/irma/blob/main/w32.go

Now that we have a viable implementation of the functions of the windows API, are we interested in what we can get out of our processes handles.

From process handle, you’ll now have to get modules. A process’s module represents a.dll or .exe file that is loaded into a particular process. Each module contains usefull informations such its name and file path. You will also be able to find out the location it occupies in memory.

// GetProcessModulesHandles list modules handles from a process handle
func GetProcessModulesHandles(procHandle windows.Handle) (processFilename string, modules []syscall.Handle, err error) {
var processRawName []byte
processRawName, err = GetProcessImageFileName(procHandle, 512)
if err != nil {
return "", nil, err
}
processRawName = bytes.Trim(processRawName, "\x00")
processPath := strings.Split(string(processRawName), "\\")
processFilename = processPath[len(processPath)-1]
modules, err = EnumProcessModules(procHandle, 32) if err != nil {
return "", nil, err
}
return processFilename, modules, nil
}

Now that you have a handle associated with each module, let’s retrieve the name of the executable or the associated library as well as its location on the disk:

procFilename, modules, err := GetProcessModulesHandles(procHandle)if err == nil {    for _, moduleHandle := range modules {
if moduleHandle != 0 {
moduleRawName, err := GetModuleFileNameEx(procHandle, moduleHandle, 512)
if err != nil{
log.Println("[ERROR]", err)
}
moduleRawName = bytes.Trim(moduleRawName, "\x00")
modulePath := strings.Split(string(moduleRawName), "\\")
moduleFileName := modulePath[len(modulePath)-1]
}
}

And finally, let’s access its memory location:

// DumpModuleMemory dump a process module memory and return it as a byte slicefunc DumpModuleMemory(procHandle windows.Handle, modHandle syscall.Handle, verbose bool) []byte {    moduleInfos, err := GetModuleInformation(procHandle, modHandle)
if err != nil{
log.Println("[ERROR]", err)
}
memdump, err := ReadProcessMemory(procHandle, moduleInfos.BaseOfDll, uintptr(moduleInfos.SizeOfImage)) if err != nil {
log.Println("[ERROR]", err)
}
memdump = bytes.Trim(memdump, "\x00")
return memdump
}

Et voilà! In the end, with relatively little code, we were able to list processes, their modules and extract the memory associated with each of them. All we have to do now is analyse this content. But that’s enough for this part and we’ll see that later. If you are interested in the finalized project, I invite you to have a look at my IRMA (Incident Response — Minimal Analysis) source code.

--

--