Skip to content

mewbak/antidebug-golang-binary

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Antidebug Golang binary on Windoze

This project is also available at acmpxyz.com/go_antidebug.html.

Debugging Golang binaries could be strange, they may be PE or ELF binaries and it is a fairly new language. In this case, the binary has an antidebug user-mode function so it can complicate our task. There are cool features (that you should read in Analyzing Golang Executables) about Go programming language but there is not main purpose... However, why is Golang used for malware development? I am not an expertise on it but my opinion is:

  • Concurrency (via goroutines) can provide us an easy way to build state graph about our infected endpoint (if we act as C2 operator). Therefore, we will be able to control more easily from a single binary. I do not know if concurrency could fix in dropper or agent; instead, parallelism (in my view) is a good feature on dropper or agent in order to decrease early detection. For instance, you can simulate many trivial events and cover up few illegitimate events. It is a good discussion. An useful talk about concurrency and parallelism is Rob Pike's talk.

  • Statically linked help us to develop malware not dependent on libraries and versioning. However, this feature could be a disadvantage if we implement a dropper or agent because due to its large size. Note that distribution has to be lightweight. Golang executables have runtime functions and they need debug symbols. However, these binaries can be stripped but do not hide much information. If you want to stripped it, check Shrink your Go binaries with this one weird trick.

  • Cross-compiling is a built-in feature in Golang toolchain. Therefore we can compile a lot of architectures. Despite this, we need to take care with some imports via syscall library because antidebug functions are often dependent on the operating system (syscall.SYS_PTRACE vs syscall.NewLazyDLL("kernel32.dll").NewProc("IsDebuggerPresent")). See the following command to check different architectures.

$ go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/386
darwin/amd64
darwin/arm
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
freebsd/arm64
illumos/amd64
js/wasm
linux/386
<brrr many more>
...
  • Simplicity of syscalls makes it easy for us kernel land communication in order to get or modify system attributes. It allows us to use Windows API or Linux API.

main-antidebug.exe

Binary executes a XOR bitwise that decrypts (or encrypts) an URL. That functionality could be use in order to ofuscate a C2 server. Before that, there is an antidebug function so it makes our task less straightforward. It is worth mentioning that binary does not contain any infectious action. Furthermore, it is not stripped so feel free in order to reverse it :)

$ md5sum main-antidebug.exe
46bd84fbc164eed7e7bae4db49a48a49  main-antidebug.exe
$ sha256sum main-antidebug.exe
0afe7690843b5a74db20ca805f5d7659960f9b4e3dd0334ad2c800e4db75e3c7  main-antidebug.exe

PE-bear static analysis

It is time to perform a trivial static analysis with PE-bear. If you want to know about PE format, see Micro$oft specification. In the following screenshot we can see binary characteristics from File Header.

File Header

  • Relocation info stripped from file IMAGE_FILE_RELOCS_STRIPPED 0x0001. Image base field is 0x400000 as we can be seen in optional header.

Image only, Windows CE, and Microsoft Windows NT and later. This indicates that the file does not contain base relocations and must therefore be loaded at its preferred base address. If the base address is not available, the loader reports an error. The default behavior of the linker is to strip base relocations from executable (EXE) files.

  • File is executable (i.e no unresolved external references) IMAGE_FILE_EXECUTABLE_IMAGE 0x0002. Okay.

Image only. This indicates that the image file is valid and can be run. If this flag is not set, it indicates a linker error.

  • App can handle >2gb addresses IMAGE_FILE_LARGE_ADDRESS_ AWARE 0x020. Okay.

Application can handle > 2-GB addresses. OKAY.

  • Debugging info stripped from file in .DBG file IMAGE_FILE_DEBUG_STRIPPED 0x0200. Okey, it makes sense... Binary is not stripped but it has not compiled with debug symbols.

Debugging information is removed from the image file.

At least it tells us that it is not a DLL or removable media! We are going to analyz Optional Header :).

Optional Header

Some features could be the following:

  • Magic Number is 0x20B so it indicates that binary runs on 64-bit address space (PE32+ format).

  • Entry Point is 0x5EC10 offset.

  • Image Base is 0x400000 but it does not matter. It is use for technical details, you should read Why is 0x00400000 the default base address for an executable?

  • Size of Stack Reserve and Commit specify total stack allocation in virtual memory and total amount of physical memory to allocate at a time respectively.

  • Size of Heap Reserve and Commit specify total heap allocation in virtual memory and the amount of physical memory to allocate at a time respectively.

Okay, we know a little more about Optional Header. Maybe, DLL Characteristics is a good field to learn about DLL image but we will not waste time (RTFM). Final step (before disassembly and debugging) could be Section Headers.

Section Header PE-bear

As we can see there are common sections (.text, .rdata, .data, .idata and .symtab). Note that .text section has more raw size than its virtual size therefore is a good indicator that binary is not packed. However, there are inconsistent sections that I do not understand; /4 /19 /32 /46 /63.... When this occurs I usually use another static analyzer as pestudio to perform double check ;)

Section Header peestudio

Sections are equals but they are discardable. In user-mode this flag of discardable and non-discardable memory is not necessary because the memory manager performs the task. However, we are unaware that binary has code that is executed in user or kernel mode. A quick look at What happens when you mark a section as DISCARDABLE? could be useful in order to understand this flag. In my view, these sections could be runtime initialization variables (code cannot be because they are not marked as executable). Perhaps IDA will give us more clues.

IDA disassembly

IDA is good choice for disassembling code and attaching a debugger. We are going to search section and function names. Back to rare sections, we can see that IDA contains the following:

IDA Sections

In the above screenshot we see six sections: .text, .rdata, .data, .idata, .data and .idata. If we compare virtual-address (these values are based on Image Base value of PE binary 0x400000, it is not really a virtual address...), rare sections after .data section 0x5AD000. We note that IDA does not detect them.

Difference between pestudio and IDA sections

In this case we focus after 0x5AD000 to see if we can find something.

IDA 0x5AD000 section

Okay, there is nothing. If you know what these sections are, please contact me via Twitter! @lfm3773. After this little research, we are going to see other stuff.

For instance, functions names are useful. We can see a lot of runtime_* and type_* functions because is statically-linked. But as we can see there are relevant functions; syscall___LazyDLL__Load, syscall____LazyDLL__NewProc, syscall_NewLazyDLL. It seems that there are functions that are not statically-linked. Check Golang documentation about LazyDLL.

A LazyDLL implements access to a single DLL. It will delay the load of the DLL until the first call to its Handle method or to one of its LazyProc's Addr method. LazyDLL is subject to the same DLL preloading attacks as documented on LoadDLL.

main-antidebug.exe functions main-antidebug.exe functions

But if we search by main string... Oops! There are main_main and main_encryptUrl functions. They also seems important.

main-antidebug.exe functions

We have found the main program! Furthermore, as we can see below, antidebug function is main_ProcIsDebuggerPresent which belongs to kernel32.dll. According to offical documentation it determines whether the calling process is being debugged by a user-mode debugger.

main-antidebug.exe main program

It is time to save the pseudo-virtual addresses because we will need them on debugging process. As we can see on second screenshot, rax register is passed as an argument. Note that it contains a code segment that will contain IsDebuggerPresent function. More details in src/syscall/dll_windows.go documentation

main-antidebug.exe main offset main-antidebug.exe lazy syscall offset main-antidebug.exe encryptUrl offset

  • main_main with 0x4A11F0
  • syscall___LazyProc__Call with 0x4A1242
  • main_encryptUrl with 0x4A1368

x64dbg debugging

Finally, we are going to set breakpoints at key locations; 0x4A11F0, 0x4A1242+5 (after function call when cmp instruction is executed) and 0x4A1368.

main-antidebug.exe main bp

Once breakpoints have been placed, we run the debugger and we will see the following:

main-antidebug.exe main breakpoints

Then, main goal of post is to bypass antidebug function, therefore we need to change stack return value of IsDebuggerPresent function. In this case, binary runs a cmp qword ptr ss:[rsp+20],0 so okay:

  1. rsp is 0x000000C0000CBEF8 + 0x20 = 0x000000C0000CBF18.
  2. 0x000000C0000CBF18 has value 1.
  3. We must change it.

main-antidebug.exe main stack with 1 main-antidebug.exe main stack with 0 main-antidebug.exe main all stack

We run the debugger and we will bypass antidebug function. Finally, last breakpoint has been reached. It is worth noting that we have set another point because we need to known function result.

main-antidebug.exe encryptUrl function

Let's move on to the next breakpoint and we have found the decoded string 0xd4ed1be5/t4n4t0$.html

main-antidebug.exe encryptUrl result

If we write this string on our browser as https://0xd4ed1be5/t4n4t0$.html, we obtain the following:

main-antidebug.exe tanatos html

URL could be build with hexadecimal characters due to RFC 1738 - Uniform Resource Locator. Code has been implemented by me and it has been used for a CTF challenge. Note that this challenge did not include antidebug function. Finally, code is shown below.


/*
Author: Luis Fueris
Date: January 24, 2021
Description: this sample code shows a function that encrypt/decrypt (please,
erase comments if you want to do cipher operation) an URL dynamically. It 
could be use to hide a C2 URL from static analysis

*/
package main

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

// Windows DLL definitions. Note that these DLL could be define in another
// file (winmods for instance)
var (
    ModKernel32 = syscall.NewLazyDLL("kernel32.dll")
    ProcIsDebuggerPresent = ModKernel32.NewProc("IsDebuggerPresent")
)

// URL length
var LEN = 23


// Implements XOR bitwise operation to URL string
//
func encryptUrl(url []string, key []byte) []byte {
    bLetter := make([]byte, 1)
    cipherLetter := make([]byte, 1)
    cipherUrl := make([]byte, LEN)

    for i, letter := range url {
        bLetter = []byte(letter)
        for j, bit := range bLetter {
            cipherLetter[j] = bit ^ key[j]
        }

        cipherUrl[i] = cipherLetter[0]
    }

    return cipherUrl
}

// Main program checks is a debugger is present and calls cipherUrl() function
// in order to decrypt malicious URL. It should be noted that XOR cipher uses
// same function to encrypt and decrypt so if you want to encrypt something, 
// please erase comments (oUrl var) and call encryptUrl() 
//
func main() {

    flag, _, _ := ProcIsDebuggerPresent.Call()
    if flag != 0 {
        os.Exit(-1)
    }

    key := []byte{1, 0, 0, 1, 0, 1, 1, 0}
    //oUrl := []string{"0", "x", "d", "4", "e", "d", "1", "b", "e", "5","/", 
    //                            "t", "4", "n", "4", "t", "0", "$",".", "h",
    //                            "t", "m", "l"}
    cUrl := []string{ "1", "y", "e", "5", "d", "e", "0", "c", "d", "4", ".", 
                                  "u", "5", "o", "5", "u", "1", "%", "/", "i", 
                                  "u", "l", "m"}
    //fmt.Printf("[!] We are going to cipher %s string\n", oUrl)
    //cUrl := encryptUrl(oUrl, key)
    //fmt.Printf("[*] Cipher URL: %s\n", cUrl)

    fmt.Printf("[!] We are going to decipher %s string\n", cUrl)
    dUrl := encryptUrl(cUrl, key)
    fmt.Printf("[*] Decipher URL: %s\n", dUrl)

    return 
}

References

  1. Pnfsoftware - ANALYZING GOLANG EXECUTABLES - GOLANG BAsics for Reverse Engineers

  2. Medium - BREAKING ALL THE RULES: USING GO TO CALL Windows API

  3. Microsoft Documentation - PE Format

  4. Microsoft Documentation - /STACK (Stack Allocations)

  5. Microsoft Documentation - /HEAP (Set Heap Size)

  6. Devblogs Microsoft - What happens when you mark a section as DISCARDABLE?

  7. StackOverflow - ELF binary analysis static vs dynamic. How does assembly code| instruction memory mapping changes?

  8. Windows Documentation - IsDebuggerPresent function (debugapi.h

Languages